diff --git a/.agents/ralph/PROMPT_build.md b/.agents/ralph/PROMPT_build.md new file mode 100644 index 0000000..cae1446 --- /dev/null +++ b/.agents/ralph/PROMPT_build.md @@ -0,0 +1,129 @@ +# Build + +You are an autonomous coding agent. Your task is to complete the work for exactly one story and record the outcome. + +## Paths +- PRD: {{PRD_PATH}} +- AGENTS (optional): {{AGENTS_PATH}} +- Progress Log: {{PROGRESS_PATH}} +- Guardrails: {{GUARDRAILS_PATH}} +- Guardrails Reference: {{GUARDRAILS_REF}} +- Context Reference: {{CONTEXT_REF}} +- Errors Log: {{ERRORS_LOG_PATH}} +- Activity Log: {{ACTIVITY_LOG_PATH}} +- Activity Logger: {{ACTIVITY_CMD}} +- No-commit: {{NO_COMMIT}} +- Repo Root: {{REPO_ROOT}} +- Run ID: {{RUN_ID}} +- Iteration: {{ITERATION}} +- Run Log: {{RUN_LOG_PATH}} +- Run Summary: {{RUN_META_PATH}} + +## Global Quality Gates (apply to every story) +{{QUALITY_GATES}} + +## Selected Story (Do not change scope) +ID: {{STORY_ID}} +Title: {{STORY_TITLE}} + +Story details: +{{STORY_BLOCK}} + +If the story details are empty or missing, STOP and report that the PRD story format could not be parsed. + +## Rules (Non-Negotiable) +- Implement **only** the work required to complete the selected story. +- Complete all tasks associated with this story (and only this story). +- Do NOT ask the user questions. +- Do NOT change unrelated code. +- Do NOT assume something is unimplemented — confirm by reading code. +- Implement completely; no placeholders or stubs. +- If No-commit is true, do NOT commit or push changes. +- Do NOT edit the PRD JSON (status is handled by the loop). +- All changes made during the run must be committed (including updates to progress/logs). + - Before committing, perform a final **security**, **performance**, and **regression** review of your changes. + +## Your Task (Do this in order) +1. Read {{GUARDRAILS_PATH}} before any code changes. +2. Read {{ERRORS_LOG_PATH}} for repeated failures to avoid. +3. Read {{PRD_PATH}} for global context (do not edit). +4. Fully audit and read all necessary files to understand the task end-to-end before implementing. Do not assume missing functionality. +5. If {{AGENTS_PATH}} exists, follow its build/test instructions. +6. Implement only the tasks that belong to {{STORY_ID}}. +7. Run verification commands listed in the story, the global quality gates, and in {{AGENTS_PATH}} (if required). +8. If the project has a build or dev workflow, run what applies: + - Frontend build: `npm run web:build` + - Rust check: `cargo check --manifest-path apps/desktop/src-tauri/Cargo.toml` + - Confirm no build errors. +9. Perform a brief audit before committing: + - **Security:** check for obvious vulnerabilities or unsafe handling introduced by your changes. + - **Performance:** check for avoidable regressions (extra queries, heavy loops, unnecessary re-renders). + - **Regression:** verify existing behavior that could be impacted still works. +10. If No-commit is false, commit changes using the `$commit` skill. + - Stage everything: `git add -A` + - Confirm a clean working tree after commit: `git status --porcelain` should be empty. + - After committing, capture the commit hash and subject using: + `git show -s --format="%h %s" HEAD`. +11. Append a progress entry to {{PROGRESS_PATH}} with run/commit/test details (format below). + If No-commit is true, skip committing and note it in the progress entry. +12. Verification report is mandatory: + - Write the story report file listed in story `verification.artifacts` (or `.codex/ralph-gitcontext/verify/{{STORY_ID}}.md`). + - Include each passCriteria line verbatim in the report. + - Include a standalone line: `VERIFIED: YES`. + +## Progress Entry Format (Append Only) +``` +## [Date/Time] - {{STORY_ID}}: {{STORY_TITLE}} +Thread: [claude session id if available, otherwise leave blank] +Run: {{RUN_ID}} (iteration {{ITERATION}}) +Run log: {{RUN_LOG_PATH}} +Run summary: {{RUN_META_PATH}} +- Guardrails reviewed: yes +- No-commit run: {{NO_COMMIT}} +- Commit: (or `none` + reason) +- Post-commit status: `clean` or list remaining files +- Verification: + - Command: -> PASS/FAIL + - Command: -> PASS/FAIL +- Files changed: + - + - +- What was implemented +- **Learnings for future iterations:** + - Patterns discovered + - Gotchas encountered + - Useful context +--- +``` + +## Completion Signal +Only output the completion signal when the **selected story** is fully complete and verified. +When the selected story is complete, output: +COMPLETE + +Otherwise, end normally without the signal. + +## Additional Guardrails +- When authoring documentation, capture the why (tests + implementation intent). +- If you learn how to run/build/test the project, update {{AGENTS_PATH}} briefly (operational only). +- Keep AGENTS operational only; progress notes belong in {{PROGRESS_PATH}}. +- If you hit repeated errors, log them in {{ERRORS_LOG_PATH}} and add a Sign to {{GUARDRAILS_PATH}} using {{GUARDRAILS_REF}} as the template. + +## Activity Logging (Required) +Log major actions to {{ACTIVITY_LOG_PATH}} using the helper: +``` +{{ACTIVITY_CMD}} "message" +``` +Log at least: +- Start of work on the story +- After major code changes +- After tests/verification +- After updating progress log + +## Browser / UI Testing (Required for Frontend Stories) +If the selected story changes UI, you MUST verify it passes the web build: +1. Run `npm run web:build` and confirm zero errors. +2. Run `npm run web:lint` for linting. +3. For e2e tests: `npm --workspace apps/web run test:e2e` + +A frontend story is NOT complete until the build passes. diff --git a/.agents/ralph/agents.sh b/.agents/ralph/agents.sh new file mode 100755 index 0000000..a25c305 --- /dev/null +++ b/.agents/ralph/agents.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# Default agent command templates (used by loop.sh and CLI). + +AGENT_CODEX_CMD="codex exec --yolo --skip-git-repo-check -" +AGENT_CODEX_INTERACTIVE_CMD="codex --yolo {prompt}" +AGENT_CLAUDE_CMD='claude -p --dangerously-skip-permissions' +AGENT_CLAUDE_INTERACTIVE_CMD="claude --dangerously-skip-permissions {prompt}" +AGENT_DROID_CMD="droid exec --skip-permissions-unsafe -f {prompt}" +AGENT_DROID_INTERACTIVE_CMD="droid --skip-permissions-unsafe {prompt}" +AGENT_OPENCODE_CMD="opencode run \"\$(cat {prompt})\"" +AGENT_OPENCODE_INTERACTIVE_CMD="opencode --prompt {prompt}" +DEFAULT_AGENT="claude" diff --git a/.agents/ralph/config.sh b/.agents/ralph/config.sh new file mode 100755 index 0000000..70c94d0 --- /dev/null +++ b/.agents/ralph/config.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +export DEFAULT_AGENT="claude" +export ACTIVITY_CMD=".agents/ralph/log-activity.sh" + +# Model for all loop iterations and PRD generation. +# Options: claude-opus-4-6 | claude-sonnet-4-6 | claude-haiku-4-5-20251001 +# Prompt is piped via stdin — no {prompt} placeholder needed. +export AGENT_CLAUDE_CMD='claude -p --model claude-opus-4-6 --dangerously-skip-permissions' diff --git a/.agents/ralph/log-activity.sh b/.agents/ralph/log-activity.sh new file mode 100755 index 0000000..1bf4671 --- /dev/null +++ b/.agents/ralph/log-activity.sh @@ -0,0 +1,15 @@ +#!/bin/bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)" +ACTIVITY_LOG="$ROOT_DIR/.ralph/activity.log" + +if [ $# -lt 1 ]; then + echo "Usage: $0 \"message\"" + exit 1 +fi + +mkdir -p "$(dirname "$ACTIVITY_LOG")" +TS=$(date '+%Y-%m-%d %H:%M:%S') +echo "[$TS] $*" >> "$ACTIVITY_LOG" diff --git a/.agents/ralph/loop.sh b/.agents/ralph/loop.sh new file mode 100755 index 0000000..ae37f74 --- /dev/null +++ b/.agents/ralph/loop.sh @@ -0,0 +1,1269 @@ +#!/bin/bash +# Ralph loop — simple, portable, single-agent +# Usage: +# ./.agents/ralph/loop.sh # build mode, default iterations +# ./.agents/ralph/loop.sh build # build mode +# ./.agents/ralph/loop.sh prd "request" # generate PRD via agent +# ./.agents/ralph/loop.sh 10 # build mode, 10 iterations +# ./.agents/ralph/loop.sh build 1 --no-commit + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(cd "${RALPH_ROOT:-${SCRIPT_DIR}/../..}" && pwd)" +CONFIG_FILE="${SCRIPT_DIR}/config.sh" + +DEFAULT_PRD_PATH=".agents/tasks/prd.json" +DEFAULT_PROGRESS_PATH=".ralph/progress.md" +DEFAULT_AGENTS_PATH="AGENTS.md" +DEFAULT_PROMPT_BUILD=".agents/ralph/PROMPT_build.md" +DEFAULT_GUARDRAILS_PATH=".ralph/guardrails.md" +DEFAULT_ERRORS_LOG_PATH=".ralph/errors.log" +DEFAULT_ACTIVITY_LOG_PATH=".ralph/activity.log" +DEFAULT_TMP_DIR=".ralph/.tmp" +DEFAULT_RUNS_DIR=".ralph/runs" +DEFAULT_GUARDRAILS_REF=".agents/ralph/references/GUARDRAILS.md" +DEFAULT_CONTEXT_REF=".agents/ralph/references/CONTEXT_ENGINEERING.md" +DEFAULT_ACTIVITY_CMD=".agents/ralph/log-activity.sh" +if [[ -n "${RALPH_ROOT:-}" ]]; then + agents_path="$RALPH_ROOT/.agents/ralph/agents.sh" +else + agents_path="$SCRIPT_DIR/agents.sh" +fi +if [[ -f "$agents_path" ]]; then + # shellcheck source=/dev/null + source "$agents_path" +fi + +DEFAULT_MAX_ITERATIONS=25 +DEFAULT_NO_COMMIT=false +DEFAULT_STALE_SECONDS=0 +PRD_REQUEST_PATH="" +PRD_INLINE="" + +# Optional config overrides (simple shell vars) +if [ -f "$CONFIG_FILE" ]; then + # shellcheck source=/dev/null + . "$CONFIG_FILE" +fi + +DEFAULT_AGENT_NAME="${DEFAULT_AGENT:-claude}" +resolve_agent_cmd() { + local name="$1" + case "$name" in + claude) + echo "${AGENT_CLAUDE_CMD:-claude -p --dangerously-skip-permissions}" + ;; + droid) + echo "${AGENT_DROID_CMD:-droid exec --skip-permissions-unsafe -f {prompt}}" + ;; + codex|"") + echo "${AGENT_CODEX_CMD:-codex exec --yolo --skip-git-repo-check -}" + ;; + *) + echo "${AGENT_CODEX_CMD:-codex exec --yolo --skip-git-repo-check -}" + ;; + esac +} +DEFAULT_AGENT_CMD="$(resolve_agent_cmd "$DEFAULT_AGENT_NAME")" + +PRD_PATH="${PRD_PATH:-$DEFAULT_PRD_PATH}" +PROGRESS_PATH="${PROGRESS_PATH:-$DEFAULT_PROGRESS_PATH}" +AGENTS_PATH="${AGENTS_PATH:-$DEFAULT_AGENTS_PATH}" +PROMPT_BUILD="${PROMPT_BUILD:-$DEFAULT_PROMPT_BUILD}" +GUARDRAILS_PATH="${GUARDRAILS_PATH:-$DEFAULT_GUARDRAILS_PATH}" +ERRORS_LOG_PATH="${ERRORS_LOG_PATH:-$DEFAULT_ERRORS_LOG_PATH}" +ACTIVITY_LOG_PATH="${ACTIVITY_LOG_PATH:-$DEFAULT_ACTIVITY_LOG_PATH}" +TMP_DIR="${TMP_DIR:-$DEFAULT_TMP_DIR}" +RUNS_DIR="${RUNS_DIR:-$DEFAULT_RUNS_DIR}" +GUARDRAILS_REF="${GUARDRAILS_REF:-$DEFAULT_GUARDRAILS_REF}" +CONTEXT_REF="${CONTEXT_REF:-$DEFAULT_CONTEXT_REF}" +ACTIVITY_CMD="${ACTIVITY_CMD:-$DEFAULT_ACTIVITY_CMD}" +AGENT_CMD="${AGENT_CMD:-$DEFAULT_AGENT_CMD}" +MAX_ITERATIONS="${MAX_ITERATIONS:-$DEFAULT_MAX_ITERATIONS}" +NO_COMMIT="${NO_COMMIT:-$DEFAULT_NO_COMMIT}" +STALE_SECONDS="${STALE_SECONDS:-$DEFAULT_STALE_SECONDS}" + +abs_path() { + local p="$1" + if [[ "$p" = /* ]]; then + echo "$p" + else + echo "$ROOT_DIR/$p" + fi +} + +PRD_PATH="$(abs_path "$PRD_PATH")" +PROGRESS_PATH="$(abs_path "$PROGRESS_PATH")" +AGENTS_PATH="$(abs_path "$AGENTS_PATH")" +PROMPT_BUILD="$(abs_path "$PROMPT_BUILD")" +GUARDRAILS_PATH="$(abs_path "$GUARDRAILS_PATH")" +ERRORS_LOG_PATH="$(abs_path "$ERRORS_LOG_PATH")" +ACTIVITY_LOG_PATH="$(abs_path "$ACTIVITY_LOG_PATH")" +TMP_DIR="$(abs_path "$TMP_DIR")" +RUNS_DIR="$(abs_path "$RUNS_DIR")" +GUARDRAILS_REF="$(abs_path "$GUARDRAILS_REF")" +CONTEXT_REF="$(abs_path "$CONTEXT_REF")" +ACTIVITY_CMD="$(abs_path "$ACTIVITY_CMD")" + +require_agent() { + local agent_cmd="${1:-$AGENT_CMD}" + local agent_bin + agent_bin="${agent_cmd%% *}" + if [ -z "$agent_bin" ]; then + echo "AGENT_CMD is empty. Set it in config.sh." + exit 1 + fi + if ! command -v "$agent_bin" >/dev/null 2>&1; then + echo "Agent command not found: $agent_bin" + case "$agent_bin" in + codex) + echo "Install: npm i -g @openai/codex" + ;; + claude) + echo "Install: npm i -g @anthropic-ai/claude-code" + ;; + droid) + echo "Install: curl -fsSL https://app.factory.ai/cli | sh" + ;; + opencode) + echo "Install: curl -fsSL https://opencode.ai/install.sh | bash" + ;; + esac + echo "Then authenticate per the CLI's instructions." + exit 1 + fi +} + +run_agent() { + local prompt_file="$1" + if [[ "$AGENT_CMD" == *"{prompt}"* ]]; then + local escaped + escaped=$(printf '%q' "$prompt_file") + local cmd="${AGENT_CMD//\{prompt\}/$escaped}" + eval "$cmd" + else + cat "$prompt_file" | eval "$AGENT_CMD" + fi +} + +run_agent_inline() { + local prompt_file="$1" + local prompt_content + prompt_content="$(cat "$prompt_file")" + local escaped + escaped=$(printf "%s" "$prompt_content" | sed "s/'/'\\\\''/g") + local cmd="${PRD_AGENT_CMD:-$AGENT_CMD}" + if [[ "$cmd" == *"{prompt}"* ]]; then + cmd="${cmd//\{prompt\}/'$escaped'}" + else + cmd="$cmd '$escaped'" + fi + eval "$cmd" +} + +MODE="build" +while [ $# -gt 0 ]; do + case "$1" in + build|prd) + MODE="$1" + shift + ;; + --prompt) + PRD_REQUEST_PATH="$2" + shift 2 + ;; + --no-commit) + NO_COMMIT=true + shift + ;; + *) + if [ "$MODE" = "prd" ]; then + PRD_INLINE="${PRD_INLINE:+$PRD_INLINE }$1" + shift + elif [[ "$1" =~ ^[0-9]+$ ]]; then + MAX_ITERATIONS="$1" + shift + else + echo "Unknown arg: $1" + exit 1 + fi + ;; + esac +done + +PROMPT_FILE="$PROMPT_BUILD" + +if [ "$MODE" = "prd" ]; then + PRD_USE_INLINE=1 + if [ -z "${PRD_AGENT_CMD:-}" ]; then + PRD_AGENT_CMD="$AGENT_CMD" + PRD_USE_INLINE=0 + fi + if [ "${RALPH_DRY_RUN:-}" != "1" ]; then + require_agent "${PRD_AGENT_CMD:-$AGENT_CMD}" + fi + + if [[ "$PRD_PATH" == *.json ]]; then + mkdir -p "$(dirname "$PRD_PATH")" "$TMP_DIR" + else + mkdir -p "$PRD_PATH" "$TMP_DIR" + fi + + if [ -z "$PRD_REQUEST_PATH" ] && [ -n "$PRD_INLINE" ]; then + PRD_REQUEST_PATH="$TMP_DIR/prd-request-$(date +%Y%m%d-%H%M%S)-$.txt" + printf '%s\n' "$PRD_INLINE" > "$PRD_REQUEST_PATH" + fi + + if [ -z "$PRD_REQUEST_PATH" ] || [ ! -f "$PRD_REQUEST_PATH" ]; then + echo "PRD request missing. Provide a prompt string or --prompt ." + exit 1 + fi + + if [ "${RALPH_DRY_RUN:-}" = "1" ]; then + if [[ "$PRD_PATH" == *.json ]]; then + if [ ! -f "$PRD_PATH" ]; then + { + echo '{' + echo ' "version": 1,' + echo ' "project": "ralph",' + echo ' "qualityGates": [],' + echo ' "stories": []' + echo '}' + } > "$PRD_PATH" + fi + fi + exit 0 + fi + + PRD_PROMPT_FILE="$TMP_DIR/prd-prompt-$(date +%Y%m%d-%H%M%S)-$.md" + { + echo "You are an autonomous coding agent." + echo "Use the \$prd skill to create a Product Requirements Document in JSON." + if [[ "$PRD_PATH" == *.json ]]; then + echo "Save the PRD to: $PRD_PATH" + else + echo "Save the PRD as JSON in directory: $PRD_PATH" + echo "Filename rules: prd-.json using 1-3 meaningful words." + echo "Examples: prd-workout-tracker.json, prd-usage-billing.json" + fi + echo "Do NOT implement anything." + echo "After creating the PRD, end with:" + echo "PRD JSON saved to . Close this chat and run \`ralph build\`." + echo "" + echo "User request:" + cat "$PRD_REQUEST_PATH" + } > "$PRD_PROMPT_FILE" + + if [ "$PRD_USE_INLINE" -eq 1 ]; then + run_agent_inline "$PRD_PROMPT_FILE" + else + run_agent "$PRD_PROMPT_FILE" + fi + exit 0 +fi + +if [ "${RALPH_DRY_RUN:-}" != "1" ]; then + require_agent +fi + +if [ ! -f "$PROMPT_FILE" ]; then + echo "Prompt not found: $PROMPT_FILE" + exit 1 +fi + +if [ "$MODE" != "prd" ] && [ ! -f "$PRD_PATH" ]; then + echo "PRD not found: $PRD_PATH" + exit 1 +fi + +mkdir -p "$(dirname "$PROGRESS_PATH")" "$TMP_DIR" "$RUNS_DIR" + +if [ ! -f "$PROGRESS_PATH" ]; then + { + echo "# Progress Log" + echo "Started: $(date)" + echo "" + echo "## Codebase Patterns" + echo "- (add reusable patterns here)" + echo "" + echo "---" + } > "$PROGRESS_PATH" +fi + +if [ ! -f "$GUARDRAILS_PATH" ]; then + { + echo "# Guardrails (Signs)" + echo "" + echo "> Lessons learned from failures. Read before acting." + echo "" + echo "## Core Signs" + echo "" + echo "### Sign: Read Before Writing" + echo "- **Trigger**: Before modifying any file" + echo "- **Instruction**: Read the file first" + echo "- **Added after**: Core principle" + echo "" + echo "### Sign: Test Before Commit" + echo "- **Trigger**: Before committing changes" + echo "- **Instruction**: Run required tests and verify outputs" + echo "- **Added after**: Core principle" + echo "" + echo "---" + echo "" + echo "## Learned Signs" + echo "" + } > "$GUARDRAILS_PATH" +fi + +if [ ! -f "$ERRORS_LOG_PATH" ]; then + { + echo "# Error Log" + echo "" + echo "> Failures and repeated issues. Use this to add guardrails." + echo "" + } > "$ERRORS_LOG_PATH" +fi + +if [ ! -f "$ACTIVITY_LOG_PATH" ]; then + { + echo "# Activity Log" + echo "" + echo "## Run Summary" + echo "" + echo "## Events" + echo "" + } > "$ACTIVITY_LOG_PATH" +fi + +RUN_TAG="$(date +%Y%m%d-%H%M%S)-$" + +render_prompt() { + local src="$1" + local dst="$2" + local story_meta="$3" + local story_block="$4" + local run_id="$5" + local iter="$6" + local run_log="$7" + local run_meta="$8" + python3 - "$src" "$dst" "$PRD_PATH" "$AGENTS_PATH" "$PROGRESS_PATH" "$ROOT_DIR" "$GUARDRAILS_PATH" "$ERRORS_LOG_PATH" "$ACTIVITY_LOG_PATH" "$GUARDRAILS_REF" "$CONTEXT_REF" "$ACTIVITY_CMD" "$NO_COMMIT" "$story_meta" "$story_block" "$run_id" "$iter" "$run_log" "$run_meta" <<'PY' +import sys +from pathlib import Path + +src = Path(sys.argv[1]).read_text() +prd, agents, progress, root = sys.argv[3:7] +guardrails = sys.argv[7] +errors_log = sys.argv[8] +activity_log = sys.argv[9] +guardrails_ref = sys.argv[10] +context_ref = sys.argv[11] +activity_cmd = sys.argv[12] +no_commit = sys.argv[13] +meta_path = sys.argv[14] if len(sys.argv) > 14 else "" +block_path = sys.argv[15] if len(sys.argv) > 15 else "" +run_id = sys.argv[16] if len(sys.argv) > 16 else "" +iteration = sys.argv[17] if len(sys.argv) > 17 else "" +run_log = sys.argv[18] if len(sys.argv) > 18 else "" +run_meta = sys.argv[19] if len(sys.argv) > 19 else "" +repl = { + "PRD_PATH": prd, + "AGENTS_PATH": agents, + "PROGRESS_PATH": progress, + "REPO_ROOT": root, + "GUARDRAILS_PATH": guardrails, + "ERRORS_LOG_PATH": errors_log, + "ACTIVITY_LOG_PATH": activity_log, + "GUARDRAILS_REF": guardrails_ref, + "CONTEXT_REF": context_ref, + "ACTIVITY_CMD": activity_cmd, + "NO_COMMIT": no_commit, + "RUN_ID": run_id, + "ITERATION": iteration, + "RUN_LOG_PATH": run_log, + "RUN_META_PATH": run_meta, +} +story = {"id": "", "title": "", "block": ""} +quality_gates = [] +if meta_path: + try: + import json + meta = json.loads(Path(meta_path).read_text()) + story["id"] = meta.get("id", "") or "" + story["title"] = meta.get("title", "") or "" + quality_gates = meta.get("quality_gates", []) or [] + except Exception: + pass +if block_path and Path(block_path).exists(): + story["block"] = Path(block_path).read_text() +repl["STORY_ID"] = story["id"] +repl["STORY_TITLE"] = story["title"] +repl["STORY_BLOCK"] = story["block"] +if quality_gates: + repl["QUALITY_GATES"] = "\n".join([f"- {g}" for g in quality_gates]) +else: + repl["QUALITY_GATES"] = "- (none)" +for k, v in repl.items(): + src = src.replace("{{" + k + "}}", v) +Path(sys.argv[2]).write_text(src) +PY +} + +select_story() { + local meta_out="$1" + local block_out="$2" + python3 - "$PRD_PATH" "$meta_out" "$block_out" "$STALE_SECONDS" <<'PY' +import json +import os +import sys +from pathlib import Path +from datetime import datetime, timezone +try: + import fcntl +except Exception: + fcntl = None + +prd_path = Path(sys.argv[1]) +meta_out = Path(sys.argv[2]) +block_out = Path(sys.argv[3]) +stale_seconds = 0 +if len(sys.argv) > 4: + try: + stale_seconds = int(sys.argv[4]) + except Exception: + stale_seconds = 0 + +if not prd_path.exists(): + meta_out.write_text(json.dumps({"ok": False, "error": "PRD not found"}, indent=2) + "\n") + block_out.write_text("") + sys.exit(0) + +def to_bool(value): + return value is True + +def normalize_status(value): + if value is None: + return "open" + return str(value).strip().lower() + +def parse_attempts(value): + try: + parsed = int(value) + except Exception: + parsed = 0 + return parsed if parsed >= 0 else 0 + +def parse_ts(value): + if not value: + return None + text = str(value).strip() + if text.endswith("Z"): + text = text[:-1] + "+00:00" + try: + return datetime.fromisoformat(text) + except Exception: + return None + +def now_iso(): + return datetime.now(timezone.utc).isoformat() + +with prd_path.open("r+", encoding="utf-8") as fh: + if fcntl is not None: + fcntl.flock(fh.fileno(), fcntl.LOCK_EX) + try: + try: + data = json.load(fh) + except Exception as exc: + meta_out.write_text(json.dumps({"ok": False, "error": f"Invalid PRD JSON: {exc}"}, indent=2) + "\n") + block_out.write_text("") + sys.exit(0) + + stories = data.get("stories") if isinstance(data, dict) else None + if not isinstance(stories, list) or not stories: + meta_out.write_text(json.dumps({"ok": False, "error": "No stories found in PRD"}, indent=2) + "\n") + block_out.write_text("") + sys.exit(0) + + defaults = data.get("defaults") if isinstance(data, dict) else {} + max_attempts = 5 + if isinstance(defaults, dict): + max_attempts = parse_attempts(defaults.get("maxAttemptsPerStory")) + if max_attempts <= 0: + max_attempts = 5 + + integrity_errors = [] + for story in stories: + if not isinstance(story, dict): + continue + status = normalize_status(story.get("status")) + passes = to_bool(story.get("passes")) + if status == "done" and not passes: + integrity_errors.append(str(story.get("id", "(unknown)"))) + + if integrity_errors: + meta_out.write_text( + json.dumps( + { + "ok": False, + "error": ( + "PRD integrity violation: status=done but passes=false for " + + ", ".join(integrity_errors) + ), + }, + indent=2, + ) + + "\n" + ) + block_out.write_text("") + sys.exit(0) + + story_index = {s.get("id"): s for s in stories if isinstance(s, dict)} + + def is_passed(story_id: str) -> bool: + target = story_index.get(story_id) + if not isinstance(target, dict): + return False + return to_bool(target.get("passes")) + + if stale_seconds > 0: + now = datetime.now(timezone.utc) + for story in stories: + if not isinstance(story, dict): + continue + if normalize_status(story.get("status")) != "in_progress": + continue + if to_bool(story.get("passes")): + continue + started = parse_ts(story.get("startedAt")) + if started is None or (now - started).total_seconds() > stale_seconds: + story["status"] = "open" + story["startedAt"] = None + story["completedAt"] = None + story["updatedAt"] = now_iso() + + candidate = None + for story in stories: + if not isinstance(story, dict): + continue + if to_bool(story.get("passes")): + continue + if normalize_status(story.get("status")) != "open": + continue + deps = story.get("dependsOn") or [] + if not isinstance(deps, list): + deps = [] + if all(is_passed(dep) for dep in deps): + candidate = story + break + + remaining = sum( + 1 for story in stories + if isinstance(story, dict) and not to_bool(story.get("passes")) + ) + + meta = { + "ok": True, + "total": len(stories), + "remaining": remaining, + "quality_gates": data.get("qualityGates", []) or [], + "max_attempts": max_attempts, + } + + if candidate: + attempts = parse_attempts(candidate.get("attempts")) + 1 + candidate["attempts"] = attempts + candidate["status"] = "in_progress" + candidate["passes"] = False + if not candidate.get("startedAt"): + candidate["startedAt"] = now_iso() + candidate["completedAt"] = None + candidate["updatedAt"] = now_iso() + + verification = candidate.get("verification") or {} + if not isinstance(verification, dict): + verification = {} + pass_criteria = verification.get("passCriteria") + if not isinstance(pass_criteria, list): + pass_criteria = candidate.get("passCriteria") + if not isinstance(pass_criteria, list): + pass_criteria = candidate.get("acceptanceCriteria") + if not isinstance(pass_criteria, list): + pass_criteria = [] + pass_criteria = [str(item) for item in pass_criteria if str(item).strip()] + + verify_artifacts = verification.get("artifacts") + if not isinstance(verify_artifacts, list): + verify_artifacts = [] + verify_artifacts = [str(item) for item in verify_artifacts if str(item).strip()] + + meta.update({ + "id": candidate.get("id", ""), + "title": candidate.get("title", ""), + "attempts": attempts, + "pass_criteria": pass_criteria, + "verify_artifacts": verify_artifacts, + }) + + depends = candidate.get("dependsOn") or [] + if not isinstance(depends, list): + depends = [] + acceptance = candidate.get("acceptanceCriteria") or [] + if not isinstance(acceptance, list): + acceptance = [] + + description = candidate.get("description") or "" + block_lines = [] + block_lines.append(f"### {candidate.get('id', '')}: {candidate.get('title', '')}") + block_lines.append(f"Status: {candidate.get('status', 'open')}") + block_lines.append( + f"Depends on: {', '.join(depends) if depends else 'None'}" + ) + block_lines.append(f"Attempt: {attempts} of {max_attempts}") + block_lines.append("") + block_lines.append("Description:") + block_lines.append(description if description else "(none)") + block_lines.append("") + block_lines.append("Acceptance Criteria:") + if acceptance: + block_lines.extend([f"- [ ] {item}" for item in acceptance]) + else: + block_lines.append("- (none)") + block_out.write_text("\n".join(block_lines).rstrip() + "\n") + else: + block_out.write_text("") + + fh.seek(0) + fh.truncate() + json.dump(data, fh, indent=2) + fh.write("\n") + fh.flush() + os.fsync(fh.fileno()) + finally: + if fcntl is not None: + fcntl.flock(fh.fileno(), fcntl.LOCK_UN) + +meta_out.write_text(json.dumps(meta, indent=2) + "\n") +PY +} + +remaining_stories() { + local meta_file="$1" + python3 - "$meta_file" <<'PY' +import json +import sys +from pathlib import Path + +data = json.loads(Path(sys.argv[1]).read_text()) +print(data.get("remaining", "unknown")) +PY +} + +remaining_from_prd() { + python3 - "$PRD_PATH" <<'PY' +import json +import sys +from pathlib import Path + +prd_path = Path(sys.argv[1]) +if not prd_path.exists(): + print("unknown") + sys.exit(0) + +try: + data = json.loads(prd_path.read_text()) +except Exception: + print("unknown") + sys.exit(0) + +stories = data.get("stories") if isinstance(data, dict) else None +if not isinstance(stories, list): + print("unknown") + sys.exit(0) + +remaining = sum( + 1 for story in stories + if isinstance(story, dict) and story.get("passes") is not True +) +print(remaining) +PY +} + +story_field() { + local meta_file="$1" + local field="$2" + python3 - "$meta_file" "$field" <<'PY' +import json +import sys +from pathlib import Path + +data = json.loads(Path(sys.argv[1]).read_text()) +field = sys.argv[2] +print(data.get(field, "")) +PY +} + +json_field() { + local json_file="$1" + local field="$2" + python3 - "$json_file" "$field" <<'PY' +import json +import sys +from pathlib import Path + +data = json.loads(Path(sys.argv[1]).read_text()) +field = sys.argv[2] +value = data.get(field) +if isinstance(value, bool): + print("true" if value else "false") +elif value is None: + print("") +elif isinstance(value, (dict, list)): + print(json.dumps(value, ensure_ascii=False)) +else: + print(value) +PY +} + +finalize_story() { + local story_id="$1" + local outcome="$2" + local note="$3" + local result_out="$4" + python3 - "$PRD_PATH" "$story_id" "$outcome" "$note" "$result_out" <<'PY' +import json +import os +import sys +from pathlib import Path +from datetime import datetime, timezone +try: + import fcntl +except Exception: + fcntl = None + +prd_path = Path(sys.argv[1]) +story_id = sys.argv[2] +outcome = sys.argv[3] +note = sys.argv[4] +result_out = Path(sys.argv[5]) if len(sys.argv) > 5 and sys.argv[5] else None + +if not story_id or not prd_path.exists(): + sys.exit(0) + +def now_iso(): + return datetime.now(timezone.utc).isoformat() + +def parse_attempts(value): + try: + parsed = int(value) + except Exception: + parsed = 0 + return parsed if parsed >= 0 else 0 + +def max_attempts_from(data): + defaults = data.get("defaults") if isinstance(data, dict) else {} + value = 5 + if isinstance(defaults, dict): + value = parse_attempts(defaults.get("maxAttemptsPerStory")) + return value if value > 0 else 5 + +def merge_note(existing, new_note): + existing = (existing or "").strip() + new_note = (new_note or "").strip() + if not new_note: + return existing + if not existing: + return new_note + if new_note in existing: + return existing + return existing + "\n" + new_note + +result = { + "ok": False, + "story_id": story_id, + "status": "", + "passes": False, + "attempts": 0, + "skipped": False, + "max_attempts": 5, + "note": note, +} + +with prd_path.open("r+", encoding="utf-8") as fh: + if fcntl is not None: + fcntl.flock(fh.fileno(), fcntl.LOCK_EX) + try: + data = json.load(fh) + stories = data.get("stories") if isinstance(data, dict) else None + if not isinstance(stories, list): + raise RuntimeError("PRD stories are missing") + + max_attempts = max_attempts_from(data) + result["max_attempts"] = max_attempts + found = None + for story in stories: + if isinstance(story, dict) and story.get("id") == story_id: + found = story + break + + if not isinstance(found, dict): + raise RuntimeError(f"Story not found: {story_id}") + + now = now_iso() + attempts = parse_attempts(found.get("attempts")) + skipped = False + + if outcome == "pass": + found["status"] = "done" + found["passes"] = True + found["skipped"] = False + if not found.get("startedAt"): + found["startedAt"] = now + found["completedAt"] = now + found["updatedAt"] = now + found["notes"] = merge_note(found.get("notes"), note) + else: + found["passes"] = False + found["updatedAt"] = now + if attempts >= max_attempts: + skipped = True + found["status"] = "done" + found["passes"] = True + found["skipped"] = True + if not found.get("startedAt"): + found["startedAt"] = now + found["completedAt"] = now + cb_note = f"CIRCUIT_BREAKER: reached max attempts ({max_attempts})." + if note: + cb_note = cb_note + " " + note + found["notes"] = merge_note(found.get("notes"), cb_note) + else: + found["status"] = "open" + found["skipped"] = False + found["startedAt"] = None + found["completedAt"] = None + found["notes"] = merge_note(found.get("notes"), note) + + result.update( + { + "ok": True, + "status": found.get("status", ""), + "passes": found.get("passes") is True, + "attempts": parse_attempts(found.get("attempts")), + "skipped": skipped, + "note": found.get("notes", ""), + } + ) + + fh.seek(0) + fh.truncate() + json.dump(data, fh, indent=2) + fh.write("\n") + fh.flush() + os.fsync(fh.fileno()) + finally: + if fcntl is not None: + fcntl.flock(fh.fileno(), fcntl.LOCK_UN) + +if result_out: + result_out.write_text(json.dumps(result, indent=2) + "\n") +else: + print(json.dumps(result)) +PY +} + +verify_story_report() { + local story_id="$1" + local result_out="$2" + python3 - "$PRD_PATH" "$story_id" "$ROOT_DIR" "$result_out" <<'PY' +import json +import re +import sys +from pathlib import Path + +prd_path = Path(sys.argv[1]) +story_id = sys.argv[2] +root_dir = Path(sys.argv[3]) +result_out = Path(sys.argv[4]) + +result = { + "ok": False, + "story_id": story_id, + "verify_path": "", + "verified_line": False, + "missing_criteria": [], + "error": "", +} + +if not prd_path.exists(): + result["error"] = f"PRD not found: {prd_path}" + result_out.write_text(json.dumps(result, indent=2) + "\n") + sys.exit(0) + +try: + data = json.loads(prd_path.read_text()) +except Exception as exc: + result["error"] = f"Invalid PRD JSON: {exc}" + result_out.write_text(json.dumps(result, indent=2) + "\n") + sys.exit(0) + +stories = data.get("stories") if isinstance(data, dict) else None +if not isinstance(stories, list): + result["error"] = "PRD stories are missing" + result_out.write_text(json.dumps(result, indent=2) + "\n") + sys.exit(0) + +story = None +for item in stories: + if isinstance(item, dict) and item.get("id") == story_id: + story = item + break + +if not isinstance(story, dict): + result["error"] = f"Story not found in PRD: {story_id}" + result_out.write_text(json.dumps(result, indent=2) + "\n") + sys.exit(0) + +verification = story.get("verification") or {} +if not isinstance(verification, dict): + verification = {} + +criteria = verification.get("passCriteria") +if not isinstance(criteria, list): + criteria = story.get("passCriteria") +if not isinstance(criteria, list): + criteria = story.get("acceptanceCriteria") +if not isinstance(criteria, list): + criteria = [] +criteria = [str(item).strip() for item in criteria if str(item).strip()] + +artifacts = verification.get("artifacts") +if not isinstance(artifacts, list): + artifacts = [] +artifacts = [str(item).strip() for item in artifacts if str(item).strip()] + +verify_rel = "" +for artifact in artifacts: + if "/verify/" in artifact and artifact.endswith(".md"): + verify_rel = artifact + break +if not verify_rel: + # Default verify path for this project + verify_rel = f".codex/ralph-gitcontext/verify/{story_id}.md" + +verify_path = Path(verify_rel) +if not verify_path.is_absolute(): + verify_path = root_dir / verify_path +result["verify_path"] = str(verify_path) + +if not verify_path.exists(): + result["error"] = f"Verification report not found: {verify_path}" + result_out.write_text(json.dumps(result, indent=2) + "\n") + sys.exit(0) + +text = verify_path.read_text(encoding="utf-8", errors="replace") +verified_line = re.search(r"(?mi)^VERIFIED:\s*YES\s*$", text) is not None +missing = [line for line in criteria if line not in text] + +result["verified_line"] = verified_line +result["missing_criteria"] = missing +result["ok"] = verified_line and not missing +if not verified_line: + result["error"] = "Verification report missing required line: VERIFIED: YES" +elif missing: + result["error"] = "Verification report is missing one or more passCriteria lines" + +result_out.write_text(json.dumps(result, indent=2) + "\n") +PY +} + +log_activity() { + local message="$1" + local timestamp + timestamp=$(date '+%Y-%m-%d %H:%M:%S') + echo "[$timestamp] $message" >> "$ACTIVITY_LOG_PATH" +} + +log_error() { + local message="$1" + local timestamp + timestamp=$(date '+%Y-%m-%d %H:%M:%S') + echo "[$timestamp] $message" >> "$ERRORS_LOG_PATH" +} + +append_run_summary() { + local line="$1" + python3 - "$ACTIVITY_LOG_PATH" "$line" <<'PY' +import sys +from pathlib import Path + +path = Path(sys.argv[1]) +line = sys.argv[2] +text = path.read_text().splitlines() +out = [] +inserted = False +for l in text: + out.append(l) + if not inserted and l.strip() == "## Run Summary": + out.append(f"- {line}") + inserted = True +if not inserted: + out = [ + "# Activity Log", + "", + "## Run Summary", + f"- {line}", + "", + "## Events", + "", + ] + text +Path(path).write_text("\n".join(out).rstrip() + "\n") +PY +} + +write_run_meta() { + local path="$1" + local mode="$2" + local iter="$3" + local run_id="$4" + local story_id="$5" + local story_title="$6" + local started="$7" + local ended="$8" + local duration="$9" + local status="${10}" + local log_file="${11}" + local head_before="${12}" + local head_after="${13}" + local commit_list="${14}" + local changed_files="${15}" + local dirty_files="${16}" + { + echo "# Ralph Run Summary" + echo "" + echo "- Run ID: $run_id" + echo "- Iteration: $iter" + echo "- Mode: $mode" + if [ -n "$story_id" ]; then + echo "- Story: $story_id: $story_title" + fi + echo "- Started: $started" + echo "- Ended: $ended" + echo "- Duration: ${duration}s" + echo "- Status: $status" + echo "- Log: $log_file" + echo "" + echo "## Git" + echo "- Head (before): ${head_before:-unknown}" + echo "- Head (after): ${head_after:-unknown}" + echo "" + echo "### Commits" + if [ -n "$commit_list" ]; then + echo "$commit_list" + else + echo "- (none)" + fi + echo "" + echo "### Changed Files (commits)" + if [ -n "$changed_files" ]; then + echo "$changed_files" + else + echo "- (none)" + fi + echo "" + echo "### Uncommitted Changes" + if [ -n "$dirty_files" ]; then + echo "$dirty_files" + else + echo "- (clean)" + fi + echo "" + } > "$path" +} + +git_head() { + if git -C "$ROOT_DIR" rev-parse --is-inside-work-tree >/dev/null 2>&1; then + git -C "$ROOT_DIR" rev-parse HEAD 2>/dev/null || true + else + echo "" + fi +} + +git_commit_list() { + local before="$1" + local after="$2" + if [ -n "$before" ] && [ -n "$after" ] && [ "$before" != "$after" ]; then + git -C "$ROOT_DIR" log --oneline "$before..$after" | sed 's/^/- /' + else + echo "" + fi +} + +git_changed_files() { + local before="$1" + local after="$2" + if [ -n "$before" ] && [ -n "$after" ] && [ "$before" != "$after" ]; then + git -C "$ROOT_DIR" diff --name-only "$before" "$after" | sed 's/^/- /' + else + echo "" + fi +} + +git_dirty_files() { + if git -C "$ROOT_DIR" rev-parse --is-inside-work-tree >/dev/null 2>&1; then + git -C "$ROOT_DIR" status --porcelain | awk '{print "- " $2}' + else + echo "" + fi +} + +echo "Ralph mode: $MODE" +echo "Max iterations: $MAX_ITERATIONS" +echo "PRD: $PRD_PATH" +HAS_ERROR="false" + +for i in $(seq 1 "$MAX_ITERATIONS"); do + echo "" + echo "═══════════════════════════════════════════════════════" + echo " Ralph Iteration $i of $MAX_ITERATIONS" + echo "═══════════════════════════════════════════════════════" + + STORY_META="" + STORY_BLOCK="" + ITER_START=$(date +%s) + ITER_START_FMT=$(date '+%Y-%m-%d %H:%M:%S') + if [ "$MODE" = "build" ]; then + STORY_META="$TMP_DIR/story-$RUN_TAG-$i.json" + STORY_BLOCK="$TMP_DIR/story-$RUN_TAG-$i.md" + select_story "$STORY_META" "$STORY_BLOCK" + SELECT_OK="$(json_field "$STORY_META" "ok")" + if [ "$SELECT_OK" != "true" ]; then + SELECT_ERROR="$(json_field "$STORY_META" "error")" + echo "Story selection failed: $SELECT_ERROR" + exit 1 + fi + REMAINING="$(remaining_stories "$STORY_META")" + if [ "$REMAINING" = "unknown" ]; then + echo "Could not parse stories from PRD: $PRD_PATH" + exit 1 + fi + if [ "$REMAINING" = "0" ]; then + echo "No remaining stories." + exit 0 + fi + STORY_ID="$(story_field "$STORY_META" "id")" + STORY_TITLE="$(story_field "$STORY_META" "title")" + if [ -z "$STORY_ID" ]; then + echo "No actionable open stories (all blocked or in progress). Remaining: $REMAINING" + exit 0 + fi + fi + + HEAD_BEFORE="$(git_head)" + PROMPT_RENDERED="$TMP_DIR/prompt-$RUN_TAG-$i.md" + LOG_FILE="$RUNS_DIR/run-$RUN_TAG-iter-$i.log" + RUN_META="$RUNS_DIR/run-$RUN_TAG-iter-$i.md" + render_prompt "$PROMPT_FILE" "$PROMPT_RENDERED" "$STORY_META" "$STORY_BLOCK" "$RUN_TAG" "$i" "$LOG_FILE" "$RUN_META" + + if [ "$MODE" = "build" ] && [ -n "${STORY_ID:-}" ]; then + log_activity "ITERATION $i start (mode=$MODE story=$STORY_ID)" + else + log_activity "ITERATION $i start (mode=$MODE)" + fi + set +e + if [ "${RALPH_DRY_RUN:-}" = "1" ]; then + echo "[RALPH_DRY_RUN] Skipping agent execution." | tee "$LOG_FILE" + CMD_STATUS=0 + else + run_agent "$PROMPT_RENDERED" 2>&1 | tee "$LOG_FILE" + CMD_STATUS=$? + fi + set -e + if [ "$CMD_STATUS" -eq 130 ] || [ "$CMD_STATUS" -eq 143 ]; then + echo "Interrupted." + exit "$CMD_STATUS" + fi + ITER_END=$(date +%s) + ITER_END_FMT=$(date '+%Y-%m-%d %H:%M:%S') + ITER_DURATION=$((ITER_END - ITER_START)) + HEAD_AFTER="$(git_head)" + log_activity "ITERATION $i end (duration=${ITER_DURATION}s)" + if [ "$CMD_STATUS" -ne 0 ]; then + log_error "ITERATION $i command failed (status=$CMD_STATUS)" + HAS_ERROR="true" + fi + STATUS_LABEL="success" + if [ "$MODE" = "build" ]; then + OUTCOME_META="$TMP_DIR/outcome-$RUN_TAG-$i.json" + if [ "$CMD_STATUS" -ne 0 ]; then + STATUS_LABEL="error" + HAS_ERROR="true" + REASON="Agent execution failed with status $CMD_STATUS; see $LOG_FILE" + log_error "ITERATION $i $REASON" + finalize_story "$STORY_ID" "fail" "$REASON" "$OUTCOME_META" + echo "Iteration failed; story re-queued (or skipped by circuit breaker)." + else + if ! grep -q "COMPLETE" "$LOG_FILE"; then + STATUS_LABEL="error" + HAS_ERROR="true" + REASON="Completion signal missing: COMPLETE." + log_error "ITERATION $i $REASON" + finalize_story "$STORY_ID" "fail" "$REASON" "$OUTCOME_META" + echo "No completion signal; story re-queued (or skipped by circuit breaker)." + else + VERIFY_META="$TMP_DIR/verify-$RUN_TAG-$i.json" + verify_story_report "$STORY_ID" "$VERIFY_META" + VERIFY_OK="$(json_field "$VERIFY_META" "ok")" + if [ "$VERIFY_OK" = "true" ]; then + finalize_story "$STORY_ID" "pass" "Completion + verification gate passed." "$OUTCOME_META" + echo "Completion signal + verification gate passed; story marked passes=true and done." + else + STATUS_LABEL="error" + HAS_ERROR="true" + VERIFY_ERROR="$(json_field "$VERIFY_META" "error")" + VERIFY_PATH="$(json_field "$VERIFY_META" "verify_path")" + REASON="Verification gate failed (${VERIFY_ERROR}). Verify report: ${VERIFY_PATH}" + log_error "ITERATION $i $REASON" + finalize_story "$STORY_ID" "fail" "$REASON" "$OUTCOME_META" + echo "Verification gate failed; story re-queued (or skipped by circuit breaker)." + fi + fi + fi + else + if [ "$CMD_STATUS" -ne 0 ]; then + STATUS_LABEL="error" + HAS_ERROR="true" + fi + fi + + COMMIT_LIST="$(git_commit_list "$HEAD_BEFORE" "$HEAD_AFTER")" + CHANGED_FILES="$(git_changed_files "$HEAD_BEFORE" "$HEAD_AFTER")" + DIRTY_FILES="$(git_dirty_files)" + if [ "$MODE" = "build" ] && [ "$NO_COMMIT" = "false" ] && [ -n "$DIRTY_FILES" ]; then + log_error "ITERATION $i left uncommitted changes; review run summary at $RUN_META" + fi + write_run_meta "$RUN_META" "$MODE" "$i" "$RUN_TAG" "${STORY_ID:-}" "${STORY_TITLE:-}" "$ITER_START_FMT" "$ITER_END_FMT" "$ITER_DURATION" "$STATUS_LABEL" "$LOG_FILE" "$HEAD_BEFORE" "$HEAD_AFTER" "$COMMIT_LIST" "$CHANGED_FILES" "$DIRTY_FILES" + if [ "$MODE" = "build" ] && [ -n "${STORY_ID:-}" ]; then + append_run_summary "$(date '+%Y-%m-%d %H:%M:%S') | run=$RUN_TAG | iter=$i | mode=$MODE | story=$STORY_ID | duration=${ITER_DURATION}s | status=$STATUS_LABEL" + else + append_run_summary "$(date '+%Y-%m-%d %H:%M:%S') | run=$RUN_TAG | iter=$i | mode=$MODE | duration=${ITER_DURATION}s | status=$STATUS_LABEL" + fi + + if [ "$MODE" = "build" ]; then + REMAINING="$(remaining_from_prd)" + echo "Iteration $i complete. Remaining stories: $REMAINING" + if [ "$REMAINING" = "0" ]; then + echo "No remaining stories." + exit 0 + fi + else + echo "Iteration $i complete." + fi + sleep 2 + +done + +echo "Reached max iterations ($MAX_ITERATIONS)." +if [ "$HAS_ERROR" = "true" ]; then + exit 1 +fi +exit 0 diff --git a/.agents/ralph/references/CONTEXT_ENGINEERING.md b/.agents/ralph/references/CONTEXT_ENGINEERING.md new file mode 100644 index 0000000..4bce5a6 --- /dev/null +++ b/.agents/ralph/references/CONTEXT_ENGINEERING.md @@ -0,0 +1,114 @@ +# Context Engineering Reference + +This document explains the malloc/free metaphor for LLM context management that underlies the Ralph technique. + +## The malloc() Metaphor + +In traditional programming: +- `malloc()` allocates memory +- `free()` releases memory +- Memory leaks occur when you allocate without freeing + +In LLM context: +- Reading files, receiving responses, tool outputs = `malloc()` +- **There is no `free()`** - context cannot be released +- The only way to "free" is to start a new conversation + +## Why This Matters + +### Context Pollution + +When you work on multiple unrelated tasks in the same context: + +``` +Task 1: Build authentication → context contains auth code, JWT docs, security patterns +Task 2: Build UI components → context now ALSO contains auth stuff + +Result: LLM might suggest auth-related patterns when building UI + or mix concerns inappropriately +``` + +### Autoregressive Failure + +LLMs predict the next token based on ALL context. When context contains: +- Unrelated information +- Failed attempts +- Mixed concerns + +The model can "spiral" into wrong territory, generating increasingly off-base responses. + +### The Gutter Metaphor + +> "If the bowling ball is in the gutter, there's no saving it." + +Once context is polluted with failed attempts or mixed concerns, the model will keep referencing that pollution. Starting fresh is often faster than trying to correct course. + +## Context Health Indicators + +### 🟢 Healthy Context +- Single focused task +- Relevant files only +- Clear progress +- Under 60% capacity + +### 🟡 Warning Signs +- Multiple unrelated topics discussed +- Several failed attempts in history +- Approaching 80% capacity +- Repeated similar errors + +### 🔴 Critical / Gutter +- Mixed concerns throughout +- Circular failure patterns +- Over 90% capacity +- Model suggesting irrelevant solutions + +## Best Practices + +### 1. One Task Per Context + +Don't ask "fix the auth bug AND add the new feature". Do them in separate conversations. + +### 2. Fresh Start on Topic Change + +Finished one story? Ralph starts a new conversation for the next one automatically. + +### 3. Don't Redline + +Stay under 80% of context capacity. Quality degrades as you approach limits. + +### 4. Recognize the Gutter + +If you're seeing: +- Same error 3+ times +- Solutions that don't match the problem +- Circular suggestions + +Start fresh. Your progress is in the files. + +### 5. State in Files, Not Context + +Write progress to files. The next conversation can read them. Context is ephemeral; files are permanent. + +## Ralph's Approach + +The original Ralph technique (`while :; do cat PROMPT.md | agent ; done`) naturally implements these principles: + +1. **Each iteration is a fresh process** - Context is freed +2. **State persists in files** - Progress survives context resets +3. **Same prompt each time** - Focused, single-task context +4. **Failures inform guardrails** - Learning without context pollution + +## When to Start Fresh + +**Definitely start fresh when:** +- Switching to unrelated task +- Context over 90% full +- Same error 3+ times +- Model suggestions are off-topic + +**Consider starting fresh when:** +- Context over 70% full +- Significant topic shift within task +- Feeling "stuck" +- Multiple failed approaches in history diff --git a/.agents/ralph/references/GUARDRAILS.md b/.agents/ralph/references/GUARDRAILS.md new file mode 100644 index 0000000..11fdca2 --- /dev/null +++ b/.agents/ralph/references/GUARDRAILS.md @@ -0,0 +1,148 @@ +# Guardrails Reference ("Signs") + +This document explains how to create and use guardrails in Ralph. + +## The Signs Metaphor + +From Geoffrey Huntley: + +> "Ralph is very good at making playgrounds, but he comes home bruised because he fell off the slide, so one then tunes Ralph by adding a sign next to the slide saying 'SLIDE DOWN, DON'T JUMP, LOOK AROUND,' and Ralph is more likely to look and see the sign." + +Signs are explicit instructions added to prevent known failure modes. + +## Anatomy of a Sign + +```markdown +### Sign: [Descriptive Name] +- **Trigger**: When this situation occurs +- **Instruction**: What to do instead +- **Added after**: When/why this was added +- **Example**: Concrete example if helpful +``` + +## Types of Signs + +### 1. Preventive Signs + +Stop problems before they happen: + +```markdown +### Sign: Validate Before Trust +- **Trigger**: When receiving external input +- **Instruction**: Always validate and sanitize input before using it +- **Added after**: Iteration 3 - SQL injection vulnerability +``` + +### 2. Corrective Signs + +Fix recurring mistakes: + +```markdown +### Sign: Check Return Values +- **Trigger**: When calling functions that can fail +- **Instruction**: Always check return values and handle errors +- **Added after**: Iteration 7 - Null pointer exception +``` + +### 3. Process Signs + +Enforce good practices: + +```markdown +### Sign: Test Before Commit +- **Trigger**: Before committing changes +- **Instruction**: Run the test suite and ensure all tests pass +- **Added after**: Iteration 2 - Broken tests committed +``` + +### 4. Architecture Signs + +Guide design decisions: + +```markdown +### Sign: Single Responsibility +- **Trigger**: When a function grows beyond 50 lines +- **Instruction**: Consider splitting into smaller, focused functions +- **Added after**: Iteration 12 - Unmaintainable god function +``` + +## When to Add Signs + +Add a sign when: + +1. **The same mistake happens twice** - Once is learning, twice is a pattern +2. **A subtle bug is found** - Prevent future occurrences +3. **A best practice is violated** - Reinforce good habits +4. **Context-specific knowledge is needed** - Project-specific conventions + +## Sign Lifecycle + +### Creation + +```markdown +### Sign: [New Sign] +- **Trigger**: [When it applies] +- **Instruction**: [What to do] +- **Added after**: Iteration N - [What happened] +``` + +### Refinement + +If a sign isn't working: +- Make the trigger more specific +- Make the instruction clearer +- Add examples + +### Retirement + +Signs can be removed when: +- The underlying issue is fixed at a deeper level +- The sign is no longer relevant +- The sign is causing more problems than it solves + +## Example Signs Library + +### Security + +```markdown +### Sign: Sanitize All Input +- **Trigger**: Any user-provided data +- **Instruction**: Use parameterized queries, escape HTML, validate types +- **Example**: `db.query("SELECT * FROM users WHERE id = ?", [userId])` +``` + +### Error Handling + +```markdown +### Sign: Graceful Degradation +- **Trigger**: External service calls +- **Instruction**: Always have a fallback for when services are unavailable +- **Example**: Cache results, provide default values, show friendly errors +``` + +### Testing + +```markdown +### Sign: Test the Unhappy Path +- **Trigger**: Writing tests for new functionality +- **Instruction**: Include tests for error cases, edge cases, and invalid input +``` + +### Code Quality + +```markdown +### Sign: Explain Why, Not What +- **Trigger**: Writing comments +- **Instruction**: Comments should explain reasoning, not describe obvious code +- **Example**: `// Using retry because API is flaky under load` not `// Call the API` +``` + +## Integration with Ralph + +Signs are: +1. Stored in `.ralph/guardrails.md` +2. Injected into context at the start of each iteration +3. Referenced when relevant situations arise +4. Updated based on observed failures + +The goal is a self-improving system where each failure makes future iterations smarter. diff --git a/.agents/tasks/prd.json b/.agents/tasks/prd.json new file mode 100644 index 0000000..b16f0b7 --- /dev/null +++ b/.agents/tasks/prd.json @@ -0,0 +1,553 @@ +{ + "version": 1, + "project": "gitcontext-desktop-tests", + "qualityGates": [ + "npm --workspace apps/desktop run test", + "cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml", + "cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings", + "npm run web:build" + ], + "defaults": { + "maxAttemptsPerStory": 5 + }, + "stories": [ + { + "id": "rust-git-open-repo", + "title": "Rust backend: open_repo command tests", + "description": "Add comprehensive Cargo tests for the open_repo Tauri command in apps/desktop/src-tauri/src/git.rs. Test: opening a valid Git repo returns branches and default_branch, opening a non-existent path returns a descriptive error, opening a directory that is not a Git repo returns a clear error, repos with no branches return an empty list, repos with many branches (>100) are handled correctly, bare repos open successfully. Create a test helper that initializes a temporary Git repo with commits and branches using the git2 crate. All tests go in a #[cfg(test)] mod at the bottom of git.rs.", + "testFiles": [ + "apps/desktop/src-tauri/src/git.rs" + ], + "acceptanceCriteria": [ + "At least 6 unit tests for open_repo covering valid repo, invalid path, non-git dir, bare repo, empty repo, many branches", + "Test helper function creates temp repos with configurable branches and commits", + "All tests pass with `cargo test`" + ], + "attempts": 1, + "status": "in_progress", + "passes": false, + "startedAt": "2026-02-22T10:38:17.796966+00:00", + "completedAt": null, + "updatedAt": "2026-02-22T10:38:17.797117+00:00" + }, + { + "id": "rust-git-diff", + "title": "Rust backend: git_diff command tests", + "description": "Add Cargo tests for the git_diff function in apps/desktop/src-tauri/src/git.rs. Test: diff between identical branches returns empty array, diff detects added files, diff detects modified files, diff detects removed files, diff detects renamed files (with find_similar), diff with __WORKDIR__ sentinel compares branch to working directory, diff with non-existent branch returns error, diff with binary files marks them correctly. Use the test helper from the open_repo story to set up repos with known file changes between branches.", + "testFiles": [ + "apps/desktop/src-tauri/src/git.rs" + ], + "acceptanceCriteria": [ + "At least 7 unit tests covering: identical branches, add, modify, remove, rename, WORKDIR sentinel, invalid branch", + "Rename detection verified via old_path field in DiffFile", + "WORKDIR test creates actual uncommitted changes in temp repo", + "All tests pass with `cargo test`" + ], + "attempts": 1, + "status": "done", + "passes": true, + "startedAt": "2026-02-22T10:41:22.974522+00:00", + "completedAt": "2026-02-22T11:06:55.483265+00:00", + "updatedAt": "2026-02-22T11:06:55.483265+00:00", + "skipped": false, + "notes": "Completion + verification gate passed." + }, + { + "id": "rust-git-read-file", + "title": "Rust backend: read_file_blob command tests", + "description": "Add Cargo tests for read_file_blob in apps/desktop/src-tauri/src/git.rs. Test: reading an existing text file returns correct content, reading a non-existent file returns notFound=true, reading a binary file returns binary=true, reading from __WORKDIR__ reads from filesystem, reading a file with non-UTF8 content performs lossy conversion, reading from a non-existent ref returns error. Verify the exact text content returned matches what was committed.", + "testFiles": [ + "apps/desktop/src-tauri/src/git.rs" + ], + "acceptanceCriteria": [ + "At least 6 tests covering: text file, missing file, binary file, WORKDIR read, non-UTF8, bad ref", + "Binary detection verified (null byte in content)", + "Lossy UTF-8 conversion produces replacement characters for invalid bytes", + "All tests pass with `cargo test`" + ], + "attempts": 1, + "status": "done", + "passes": true, + "startedAt": "2026-02-22T11:06:57.857228+00:00", + "completedAt": "2026-02-22T11:14:00.742936+00:00", + "updatedAt": "2026-02-22T11:14:00.742936+00:00", + "skipped": false, + "notes": "Completion + verification gate passed." + }, + { + "id": "rust-git-list-files", + "title": "Rust backend: list_files and list_files_with_oids tests", + "description": "Add Cargo tests for list_files and list_files_with_oids in apps/desktop/src-tauri/src/git.rs. Test: listing files for a ref returns all committed files, listing with __WORKDIR__ returns filesystem files (respecting .gitignore), nested directory structures flatten to full paths, empty tree returns empty list, list_files_with_oids returns valid OID strings for each file. Also test resolve_ref: valid branch resolves to OID, invalid ref returns error, HEAD resolves correctly.", + "testFiles": [ + "apps/desktop/src-tauri/src/git.rs" + ], + "acceptanceCriteria": [ + "At least 6 tests covering list_files, list_files_with_oids, and resolve_ref", + "WORKDIR listing respects .gitignore patterns", + "OIDs are valid 40-char hex strings", + "All tests pass with `cargo test`" + ], + "attempts": 1, + "status": "done", + "passes": true, + "startedAt": "2026-02-22T11:14:03.006288+00:00", + "completedAt": "2026-02-22T11:20:27.077114+00:00", + "updatedAt": "2026-02-22T11:20:27.077114+00:00", + "skipped": false, + "notes": "Completion + verification gate passed." + }, + { + "id": "rust-watcher", + "title": "Rust backend: file watcher tests", + "description": "Add Cargo tests for the watcher module in apps/desktop/src-tauri/src/watcher.rs. Test: watcher ignores changes in node_modules directory, watcher ignores .git/objects changes, watcher detects changes to regular source files and emits workdir-changed, watcher detects .git/refs changes and emits refs-changed, watcher can be stopped cleanly. If direct testing of the watcher is difficult due to Tauri AppHandle dependency, extract the path-filtering logic into a testable pure function and test that instead.", + "testFiles": [ + "apps/desktop/src-tauri/src/watcher.rs" + ], + "acceptanceCriteria": [ + "Path filtering logic is tested: node_modules filtered, .git/objects filtered, .git/logs filtered, target/ filtered", + "Regular file paths pass the filter", + ".git/refs and .git/HEAD paths trigger refs-changed classification", + "All tests pass with `cargo test`" + ], + "attempts": 1, + "status": "done", + "passes": true, + "startedAt": "2026-02-22T11:20:29.332696+00:00", + "completedAt": "2026-02-22T11:27:41.472199+00:00", + "updatedAt": "2026-02-22T11:27:41.472199+00:00", + "skipped": false, + "notes": "Completion + verification gate passed." + }, + { + "id": "tauri-git-service", + "title": "Frontend: TauriGitService unit tests", + "description": "Create apps/desktop/src/services/TauriGitService.test.ts with Vitest tests for the TauriGitService class. Mock @tauri-apps/api/core invoke function. Test: loadRepo calls invoke('open_repo') with correct args and maps result, listBranches calls invoke('get_branches'), getDiff passes base/compare correctly, readFile maps result to {binary, text, notFound}, listFiles returns flat array, resolveRef returns OID string, dispose clears repo path so subsequent calls throw or handle gracefully, singleton pattern returns same instance. Verify error propagation when invoke rejects.", + "testFiles": [ + "apps/desktop/src/services/TauriGitService.test.ts" + ], + "acceptanceCriteria": [ + "At least 8 tests covering all GitService interface methods plus dispose and singleton", + "All invoke calls verified with correct command names and argument shapes", + "Error propagation tested for at least one method", + "All tests pass with `npm --workspace apps/desktop run test`" + ], + "attempts": 1, + "status": "done", + "passes": true, + "startedAt": "2026-02-22T11:27:43.854484+00:00", + "completedAt": "2026-02-22T11:36:17.561544+00:00", + "updatedAt": "2026-02-22T11:36:17.561544+00:00", + "skipped": false, + "notes": "Completion + verification gate passed." + }, + { + "id": "use-git-repository-hook", + "title": "Frontend: useGitRepository hook tests", + "description": "Create apps/desktop/src/hooks/useGitRepository.test.ts with Vitest + React Testing Library renderHook tests. Mock the TauriGitService and @tauri-apps/api/event. Test: initial state is idle with empty branches, loadRepoFromHandle transitions state idle\u2192loading\u2192ready, loadRepoFromHandle sets branches and default branch selection, loadRepoFromHandle with invalid path sets error state, refreshRepo reloads branches and preserves selection, resetRepo clears all state back to idle, branch persistence saves to and restores from localStorage, file watcher events (workdir-changed) trigger diffTrigger increment, flipBranches swaps base and compare. Mock localStorage for persistence tests.", + "testFiles": [ + "apps/desktop/src/hooks/useGitRepository.test.ts" + ], + "acceptanceCriteria": [ + "At least 8 tests covering: initial state, load success, load error, refresh, reset, branch persistence, watcher events, flip", + "State transitions verified (idle\u2192loading\u2192ready, idle\u2192loading\u2192error)", + "localStorage mock verifies branch persistence per repo path", + "All tests pass with `npm --workspace apps/desktop run test`" + ], + "attempts": 1, + "status": "done", + "passes": true, + "startedAt": "2026-02-22T11:36:19.942961+00:00", + "completedAt": "2026-02-22T11:51:14.443562+00:00", + "updatedAt": "2026-02-22T11:51:14.443562+00:00", + "skipped": false, + "notes": "Completion + verification gate passed." + }, + { + "id": "use-file-tree-comprehensive", + "title": "Frontend: useFileTree hook comprehensive tests", + "description": "Expand apps/desktop/src/hooks/useFileTree.test.ts with comprehensive tests beyond the existing 3. Test: computeDiffAndTree builds correct hierarchical tree from flat file list, status markers (add/modify/remove/unchanged) are set correctly on nodes, binary files detected by extension get isLikelyBinary flag, showChangedOnly filter hides unchanged files, toggleExpand expands and collapses directories, toggleSelect selects and deselects files and updates parent counts, selectAll respects treeFilter and showChangedOnly, deselectAll works correctly, large repo mode (>1000 files) auto-enables showChangedOnly, race condition prevention ignores stale diff results, expandAll/collapseAll set all directory paths, diffSequence increments on each computation, revealPath expands parent directories of target file.", + "testFiles": [ + "apps/desktop/src/hooks/useFileTree.test.ts" + ], + "acceptanceCriteria": [ + "At least 12 total tests (including existing 3) covering tree building, status, binary detection, filtering, selection, large repo, race conditions", + "Tree structure verified: directories contain children, files are leaves", + "Race condition test verifies stale results are discarded", + "All tests pass with `npm --workspace apps/desktop run test`" + ], + "attempts": 1, + "status": "done", + "passes": true, + "startedAt": "2026-02-22T11:51:16.817481+00:00", + "completedAt": "2026-02-22T12:05:26.276523+00:00", + "updatedAt": "2026-02-22T12:05:26.276523+00:00", + "skipped": false, + "notes": "Completion + verification gate passed." + }, + { + "id": "use-token-counts-hook", + "title": "Frontend: useTokenCounts hook tests", + "description": "Create apps/desktop/src/hooks/useTokenCounts.test.ts. Mock the tiktoken module and GitService readFile. Test: token counts update when selectedPaths change, binary files are counted as path-only tokens when includeBinaryPaths is true, binary files return 0 tokens when includeBinaryPaths is false, progress callback reports correct completed/total, cancellation via AbortSignal stops in-flight counting, empty selection returns all zero counts, concurrent file reads respect MAX_CONCURRENT_READS limit, diff context lines affect token count (more context = more tokens), total includes file content + header formatting tokens.", + "testFiles": [ + "apps/desktop/src/hooks/useTokenCounts.test.ts" + ], + "acceptanceCriteria": [ + "At least 7 tests covering: count update, binary handling (both modes), progress, cancellation, empty selection, context lines effect", + "Mock tiktoken returns deterministic token counts", + "Cancellation test verifies no state update after abort", + "All tests pass with `npm --workspace apps/desktop run test`" + ], + "attempts": 1, + "status": "done", + "passes": true, + "startedAt": "2026-02-22T12:05:28.661571+00:00", + "completedAt": "2026-02-22T12:17:01.333179+00:00", + "updatedAt": "2026-02-22T12:17:01.333179+00:00", + "skipped": false, + "notes": "Completion + verification gate passed." + }, + { + "id": "diff-util-tests", + "title": "Frontend: diff utility tests", + "description": "Create apps/desktop/src/utils/diff.test.ts with Vitest tests for createUnifiedDiffForPath and buildUnifiedDiffForStatus. Test: modified file produces correct unified diff with context lines, added file shows all lines as additions, removed file shows all lines as deletions, unchanged file returns empty string, CRLF normalization (\\r\\n \u2192 \\n) produces stable diffs, context lines parameter controls number of surrounding lines (0, 3, 999/infinity), empty files produce valid diffs, files with no trailing newline handled correctly, binary file inputs return null/empty.", + "testFiles": [ + "apps/desktop/src/utils/diff.test.ts" + ], + "acceptanceCriteria": [ + "At least 8 tests covering: modify, add, remove, unchanged, CRLF, context lines, empty files, no trailing newline", + "Context lines verified: setting 0 shows only changed lines, 3 shows 3 lines around changes, 999 shows full file", + "CRLF normalization verified with mixed line endings", + "All tests pass with `npm --workspace apps/desktop run test`" + ], + "attempts": 1, + "status": "done", + "passes": true, + "startedAt": "2026-02-22T12:17:03.702534+00:00", + "completedAt": "2026-02-22T12:25:06.322187+00:00", + "updatedAt": "2026-02-22T12:25:06.322187+00:00", + "skipped": false, + "notes": "Completion + verification gate passed." + }, + { + "id": "workspace-store-comprehensive", + "title": "Frontend: workspaceStore comprehensive tests", + "description": "Expand apps/desktop/src/utils/workspaceStore.test.ts with additional edge case tests. Test: upsertWorkspace with existing ID updates rather than creates duplicate, removeWorkspace with non-existent ID is a no-op, getWorkspaceSelectionRestore correctly partitions matched vs missing paths (case-sensitive), MAX_PERSISTED_SELECTIONS (5000) limit is enforced on save, listWorkspaceItems returns sorted by lastOpenedAt, setActiveWorkspace/getActiveWorkspace round-trips correctly, corrupt localStorage data handled gracefully (returns empty store), workspace session settings merge with defaults (missing keys filled in), findWorkspaceByPath matches exact paths only. Fix any bugs discovered during testing.", + "testFiles": [ + "apps/desktop/src/utils/workspaceStore.test.ts" + ], + "acceptanceCriteria": [ + "At least 12 total tests (including existing 5) covering all edge cases", + "Selection limit enforcement verified (paths truncated at 5000)", + "Corrupt data handling verified (graceful degradation)", + "All tests pass with `npm --workspace apps/desktop run test`" + ], + "attempts": 1, + "status": "done", + "passes": true, + "startedAt": "2026-02-22T12:25:08.684257+00:00", + "completedAt": "2026-02-22T12:32:44.406270+00:00", + "updatedAt": "2026-02-22T12:32:44.406270+00:00", + "skipped": false, + "notes": "Completion + verification gate passed." + }, + { + "id": "concurrency-util-tests", + "title": "Frontend: concurrency utility tests", + "description": "Create apps/desktop/src/utils/concurrency.test.ts. Test mapWithConcurrency: respects concurrency limit (e.g., limit=2 never has more than 2 in-flight), processes all items and returns results in order, handles rejected promises (propagates error), empty array returns empty array, limit of 1 processes sequentially, limit greater than array length works correctly. Test createConcurrencyLimiter if it exists. Use timing/tracking to verify concurrent execution counts.", + "testFiles": [ + "apps/desktop/src/utils/concurrency.test.ts" + ], + "acceptanceCriteria": [ + "At least 5 tests covering: concurrency limit, result ordering, error propagation, empty input, sequential (limit=1)", + "Concurrency limit verified by tracking in-flight count during execution", + "Result ordering matches input ordering regardless of completion order", + "All tests pass with `npm --workspace apps/desktop run test`" + ], + "attempts": 1, + "status": "done", + "passes": true, + "startedAt": "2026-02-22T12:32:46.691635+00:00", + "completedAt": "2026-02-22T12:41:35.538484+00:00", + "updatedAt": "2026-02-22T12:41:35.538484+00:00", + "skipped": false, + "notes": "Completion + verification gate passed." + }, + { + "id": "debounce-util-tests", + "title": "Frontend: debounce utility tests", + "description": "Create apps/desktop/src/utils/debounce.test.ts. Test the debounce function: calling multiple times within delay only invokes once, final call's arguments are used, waiting full delay triggers the function, rapid calls reset the timer, returned function is callable. Use vi.useFakeTimers() for deterministic timing control.", + "testFiles": [ + "apps/desktop/src/utils/debounce.test.ts" + ], + "acceptanceCriteria": [ + "At least 4 tests covering: single invocation after burst, correct arguments, timer reset, delay expiration", + "Uses vi.useFakeTimers for reliable timing", + "All tests pass with `npm --workspace apps/desktop run test`" + ], + "attempts": 1, + "status": "done", + "passes": true, + "startedAt": "2026-02-22T12:41:37.905594+00:00", + "completedAt": "2026-02-22T12:51:23.992771+00:00", + "updatedAt": "2026-02-22T12:51:23.992771+00:00", + "skipped": false, + "notes": "Completion + verification gate passed." + }, + { + "id": "clipboard-batch-select-comprehensive", + "title": "Frontend: clipboardBatchSelect comprehensive tests", + "description": "Expand apps/desktop/src/utils/clipboardBatchSelect.test.ts with edge cases. Test: Windows drive letter paths (C:\\Users\\...) normalized correctly, UNC paths (\\\\server\\share) handled, trailing slashes stripped, empty lines and whitespace-only lines filtered, duplicate paths deduplicated, case-insensitive matching on Windows-style paths, paths with special characters (spaces, unicode), relative paths (./src/file.ts) resolved, paths outside repo root rejected with correct outsideRepoCount. Fix any bugs discovered.", + "testFiles": [ + "apps/desktop/src/utils/clipboardBatchSelect.test.ts" + ], + "acceptanceCriteria": [ + "At least 10 total tests (including existing 5) covering Windows paths, UNC, duplicates, special chars, relative paths", + "Cross-platform path handling verified", + "All tests pass with `npm --workspace apps/desktop run test`" + ], + "attempts": 1, + "status": "done", + "passes": true, + "startedAt": "2026-02-22T12:51:26.358806+00:00", + "completedAt": "2026-02-22T13:01:13.061853+00:00", + "updatedAt": "2026-02-22T13:01:13.061853+00:00", + "skipped": false, + "notes": "Completion + verification gate passed." + }, + { + "id": "selected-files-panel-tests", + "title": "Frontend: SelectedFilesPanel component tests", + "description": "Create apps/desktop/src/components/SelectedFilesPanel.test.tsx. Test: renders list of selected files with correct status icons, sorting by tokens (high\u2192low, low\u2192high) reorders files, sorting by name (A\u2192Z, Z\u2192A) reorders files, binary files show binary indicator and preview button is disabled, clicking remove calls onUnselect with correct path, clicking reveal calls onReveal with correct path, clicking preview calls onPreview with path and status, empty selection shows empty state message, refreshing state shows recalculating indicator. Must wrap in TokenCountsProvider with mocked context values.", + "testFiles": [ + "apps/desktop/src/components/SelectedFilesPanel.test.tsx" + ], + "acceptanceCriteria": [ + "At least 7 tests covering: rendering, sort modes, binary files, remove/reveal/preview callbacks, empty state", + "Sorting verified by checking DOM order after sort change", + "TokenCountsContext properly mocked", + "All tests pass with `npm --workspace apps/desktop run test`" + ], + "attempts": 1, + "status": "done", + "passes": true, + "startedAt": "2026-02-22T13:01:15.429018+00:00", + "completedAt": "2026-02-22T13:13:54.608878+00:00", + "updatedAt": "2026-02-22T13:13:54.608878+00:00", + "skipped": false, + "notes": "Completion + verification gate passed." + }, + { + "id": "context-footer-tests", + "title": "Frontend: ContextFooter component tests", + "description": "Create apps/desktop/src/components/ContextFooter.test.tsx. Test: displays correct token breakdown (files, content, instructions, file tree, total), progress bar width matches percentage of limit, over-limit state shows warning text in red, copy button disabled when no files selected, clicking copy calls onCopy, copy flash message appears and shows correct text (success/failure), zero limit hides progress bar, token counts formatted with commas for readability.", + "testFiles": [ + "apps/desktop/src/components/ContextFooter.test.tsx" + ], + "acceptanceCriteria": [ + "At least 7 tests covering: token breakdown display, progress bar, over-limit warning, disabled state, copy callback, flash messages", + "Over-limit correctly detected when total > limit", + "All tests pass with `npm --workspace apps/desktop run test`" + ], + "attempts": 1, + "status": "done", + "passes": true, + "startedAt": "2026-02-22T13:13:56.868643+00:00", + "completedAt": "2026-02-22T13:24:53.650886+00:00", + "updatedAt": "2026-02-22T13:24:53.650886+00:00", + "skipped": false, + "notes": "Completion + verification gate passed." + }, + { + "id": "right-panel-tabs-tests", + "title": "Frontend: RightPanelTabs component tests", + "description": "Create apps/desktop/src/components/RightPanelTabs.test.tsx. Test: renders two tabs (Selected Files, Settings), files tab shows badge with file count, clicking tab calls onTabChange with correct tab ID, active tab has active styling, badge shows 0 when no files, children content renders in tab panel, switching tabs preserves content (no remount).", + "testFiles": [ + "apps/desktop/src/components/RightPanelTabs.test.tsx" + ], + "acceptanceCriteria": [ + "At least 5 tests covering: tab rendering, badge count, tab switching, active styling, children rendering", + "Tab IDs 'files' and 'settings' verified", + "All tests pass with `npm --workspace apps/desktop run test`" + ], + "attempts": 1, + "status": "done", + "passes": true, + "startedAt": "2026-02-22T13:24:56.019225+00:00", + "completedAt": "2026-02-22T13:35:39.778048+00:00", + "updatedAt": "2026-02-22T13:35:39.778048+00:00", + "skipped": false, + "notes": "Completion + verification gate passed." + }, + { + "id": "token-counts-context-tests", + "title": "Frontend: TokenCountsContext provider tests", + "description": "Create apps/desktop/src/context/TokenCountsContext.test.tsx. Test: provider makes token counts available to children via useContext, counts update when selectedPaths change, busy flag is true during computation and false when done, progress reports completed/total/percent correctly, total is sum of all individual file counts, provider handles unmount during async counting gracefully (no state update after unmount). Mock the underlying useTokenCounts hook.", + "testFiles": [ + "apps/desktop/src/context/TokenCountsContext.test.tsx" + ], + "acceptanceCriteria": [ + "At least 5 tests covering: context availability, count updates, busy flag, progress, unmount safety", + "No 'state update on unmounted component' warnings", + "All tests pass with `npm --workspace apps/desktop run test`" + ], + "attempts": 1, + "status": "done", + "passes": true, + "startedAt": "2026-02-22T13:35:42.154524+00:00", + "completedAt": "2026-02-22T13:46:10.790242+00:00", + "updatedAt": "2026-02-22T13:46:10.790242+00:00", + "skipped": false, + "notes": "Completion + verification gate passed." + }, + { + "id": "theme-management-tests", + "title": "Frontend: theme management integration tests", + "description": "Create apps/desktop/src/__tests__/theme.test.ts testing theme-related logic. Test: default theme follows system preference (prefers-color-scheme), toggling theme switches between light and dark, theme persists to localStorage key 'gc.theme', removing theme preference falls back to system, data-theme attribute set on document.documentElement, corrupt localStorage theme value falls back to system default. Mock window.matchMedia and localStorage.", + "testFiles": [ + "apps/desktop/src/__tests__/theme.test.ts" + ], + "acceptanceCriteria": [ + "At least 5 tests covering: system default, toggle, persistence, fallback, DOM attribute", + "matchMedia mock supports both light and dark system preferences", + "All tests pass with `npm --workspace apps/desktop run test`" + ], + "attempts": 1, + "status": "done", + "passes": true, + "startedAt": "2026-02-22T13:46:13.141526+00:00", + "completedAt": "2026-02-22T13:56:45.587525+00:00", + "updatedAt": "2026-02-22T13:56:45.587525+00:00", + "skipped": false, + "notes": "Completion + verification gate passed." + }, + { + "id": "app-copy-output-tests", + "title": "Frontend: copy output generation tests", + "description": "Create apps/desktop/src/__tests__/copyOutput.test.ts testing the output format produced by copyAllSelected logic. Extract the output-building logic into a testable function if needed. Test: output header includes repo path, base/compare branches, file count, user instructions appear in ## Instructions section when non-empty, file tree section included when includeFileTree is true and omitted when false, modified files produce ```diff blocks, added files with unlimited context produce ``` blocks (full content), removed files produce diff blocks, binary files show [Binary file] placeholder, includeBinaryAsPaths includes binary file paths in output, context lines setting affects diff output length, multiple files concatenated in order.", + "testFiles": [ + "apps/desktop/src/__tests__/copyOutput.test.ts" + ], + "acceptanceCriteria": [ + "At least 8 tests covering: header, instructions, file tree toggle, modify/add/remove diffs, binary handling, context lines", + "Output format matches exact markdown structure used by the app", + "Binary file handling verified for both includeBinaryAsPaths settings", + "All tests pass with `npm --workspace apps/desktop run test`" + ], + "attempts": 1, + "status": "done", + "passes": true, + "startedAt": "2026-02-22T13:56:47.960378+00:00", + "completedAt": "2026-02-22T14:14:02.781153+00:00", + "updatedAt": "2026-02-22T14:14:02.781153+00:00", + "skipped": false, + "notes": "Completion + verification gate passed." + }, + { + "id": "models-util-tests", + "title": "Frontend: models utility tests", + "description": "Create apps/desktop/src/utils/models.test.ts. Test: getModels returns array of ModelInfo objects, each model has required fields (id, name, context_length), model IDs are unique, context_length values are positive numbers, model list is not empty. Also test model selection persistence: selectedModel saved to localStorage key 'gc.selectedModel', restoring selected model from localStorage works, invalid saved model ID handled gracefully.", + "testFiles": [ + "apps/desktop/src/utils/models.test.ts" + ], + "acceptanceCriteria": [ + "At least 5 tests covering: model list structure, required fields, unique IDs, persistence, invalid saved model", + "ModelInfo type compliance verified", + "All tests pass with `npm --workspace apps/desktop run test`" + ], + "attempts": 1, + "status": "done", + "passes": true, + "startedAt": "2026-02-22T14:14:05.159901+00:00", + "completedAt": "2026-02-22T14:22:39.983361+00:00", + "updatedAt": "2026-02-22T14:22:39.983361+00:00", + "skipped": false, + "notes": "Completion + verification gate passed." + }, + { + "id": "error-handling-tests", + "title": "Frontend: global error handling and ErrorBanner integration tests", + "description": "Expand apps/desktop/src/components/ErrorBanner.test.tsx with integration-level tests. Add tests: unhandled promise rejection displays in error banner, window.error event displays in error banner, error banner auto-clears when new repo loads successfully, multiple rapid errors show only the latest, error from loadRepoFromHandle propagates to banner, error from computeDiffAndTree propagates to banner, dismissing error clears it and does not reappear unless a new error occurs. Test the error recovery flow: error state \u2192 user action \u2192 success state.", + "testFiles": [ + "apps/desktop/src/components/ErrorBanner.test.tsx" + ], + "acceptanceCriteria": [ + "At least 12 total tests (including existing 7) covering error propagation, auto-clear, dismiss, recovery flow", + "Error messages from different sources verified (repo load, diff compute, unhandled rejection)", + "All tests pass with `npm --workspace apps/desktop run test`" + ], + "attempts": 1, + "status": "done", + "passes": true, + "startedAt": "2026-02-22T14:22:42.345387+00:00", + "completedAt": "2026-02-22T14:33:49.596399+00:00", + "updatedAt": "2026-02-22T14:33:49.596399+00:00", + "skipped": false, + "notes": "Completion + verification gate passed." + }, + { + "id": "workspace-lifecycle-tests", + "title": "Frontend: workspace save/restore lifecycle integration tests", + "description": "Create apps/desktop/src/__tests__/workspaceLifecycle.test.ts testing the full workspace flow. Test: saving a workspace captures current branch selection and file selection, loading a saved workspace restores branches and triggers diff, workspace auto-detection on repo open matches by path, workspace refresh preserves current selection, deleting a workspace removes it from store and clears selection, workspace with missing branch falls back gracefully, workspace session auto-persists on settings change (debounced), switching workspaces cancels pending selection restore from previous workspace. Mock TauriGitService and localStorage.", + "testFiles": [ + "apps/desktop/src/__tests__/workspaceLifecycle.test.ts" + ], + "acceptanceCriteria": [ + "At least 7 tests covering: save, restore, auto-detect, refresh, delete, missing branch, session auto-persist", + "Branch and selection state correctly captured and restored", + "Race condition between workspace switches handled", + "All tests pass with `npm --workspace apps/desktop run test`" + ], + "attempts": 1, + "status": "done", + "passes": true, + "startedAt": "2026-02-22T14:33:51.962558+00:00", + "completedAt": "2026-02-22T14:47:53.569249+00:00", + "updatedAt": "2026-02-22T14:47:53.569249+00:00", + "skipped": false, + "notes": "Completion + verification gate passed." + }, + { + "id": "logger-util-tests", + "title": "Frontend: logger utility tests", + "description": "Create apps/desktop/src/utils/logger.test.ts. Test: logError calls console.error with formatted message including error code, logError handles Error objects extracting message, logError handles string errors, logError handles non-Error non-string values (objects, null, undefined), error code prefix is included in output. Mock console.error to verify output format.", + "testFiles": [ + "apps/desktop/src/utils/logger.test.ts" + ], + "acceptanceCriteria": [ + "At least 4 tests covering: Error objects, strings, other types, error code formatting", + "console.error mock verifies exact output format", + "All tests pass with `npm --workspace apps/desktop run test`" + ], + "attempts": 1, + "status": "done", + "passes": true, + "startedAt": "2026-02-22T14:47:55.950045+00:00", + "completedAt": "2026-02-22T14:56:10.328516+00:00", + "updatedAt": "2026-02-22T14:56:10.328516+00:00", + "skipped": false, + "notes": "Completion + verification gate passed." + }, + { + "id": "diff-control-bar-comprehensive", + "title": "Frontend: DiffControlBar comprehensive interaction tests", + "description": "Expand apps/desktop/src/components/DiffControlBar.test.tsx with additional interaction tests. Test: changing base branch to same value as compare auto-swaps (if applicable), __WORKDIR__ option shows 'My Working Directory' label, workspace save button triggers save callback, workspace delete button shows only when workspace selected, refresh button calls onRefresh, all controls disabled during loading state, workspace selector dropdown shows saved workspace names sorted correctly, selecting empty workspace ID clears selection. Fix any UI bugs found.", + "testFiles": [ + "apps/desktop/src/components/DiffControlBar.test.tsx" + ], + "acceptanceCriteria": [ + "At least 28 total tests (including existing 23) covering additional interaction edge cases", + "WORKDIR display text verified", + "Disabled state comprehensively tested", + "All tests pass with `npm --workspace apps/desktop run test`" + ], + "attempts": 1, + "status": "done", + "passes": true, + "startedAt": "2026-02-22T14:56:12.699520+00:00", + "completedAt": "2026-02-22T15:04:34.879903+00:00", + "updatedAt": "2026-02-22T15:04:34.879903+00:00", + "skipped": false, + "notes": "Completion + verification gate passed." + } + ] +} diff --git a/.codex/ralph-gitcontext/loop.sh b/.codex/ralph-gitcontext/loop.sh new file mode 100755 index 0000000..e26d110 --- /dev/null +++ b/.codex/ralph-gitcontext/loop.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +PRD_PATH="$ROOT_DIR/.agents/tasks/prd-gitcontext.json" +LOG_DIR="$ROOT_DIR/.codex/ralph-gitcontext/logs" +VERIFY_DIR="$ROOT_DIR/.codex/ralph-gitcontext/verify" + +mkdir -p "$LOG_DIR" "$VERIFY_DIR" +cd "$ROOT_DIR" + +if ! command -v ralph >/dev/null 2>&1; then + echo "Error: ralph is not installed or not in PATH" >&2 + echo "Install: npm i -g @iannuttall/ralph" >&2 + exit 1 +fi + +if ! command -v jq >/dev/null 2>&1; then + echo "Error: jq is not installed or not in PATH" >&2 + exit 1 +fi + +if ! command -v claude >/dev/null 2>&1; then + echo "Error: claude is not installed or not in PATH" >&2 + echo "Install: npm i -g @anthropic-ai/claude-code" >&2 + exit 1 +fi + +if [ ! -f "$PRD_PATH" ]; then + echo "Error: PRD file not found at $PRD_PATH" >&2 + echo "Create one with: ./.agents/ralph/loop.sh prd \"\"" >&2 + exit 1 +fi + +if ! git -c user.useConfigOnly=true var GIT_AUTHOR_IDENT >/dev/null 2>&1; then + cat >&2 <<'EOF' +Error: git author identity is not configured in this container. +Loop checkpoint commits run after each iteration and require identity. + +Configure once (persists in repo .git/config): + git config user.name "Claude Loop" + git config user.email "claude-loop@local" + +Or export environment variables for this shell: + export GIT_AUTHOR_NAME="Claude Loop" + export GIT_AUTHOR_EMAIL="claude-loop@local" + export GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME" + export GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL" +EOF + exit 1 +fi + +if ! git diff --quiet || ! git diff --cached --quiet; then + echo "Error: working tree is not clean. Commit or stash existing changes before starting loop." >&2 + git status --short >&2 + exit 1 +fi + +while true; do + integrity_count="$(jq '[.stories[] | select((((.status // "open") | ascii_downcase) == "done") and (.passes != true))] | length' "$PRD_PATH")" + if [ "$integrity_count" -ne 0 ]; then + echo "Error: PRD integrity violation (status=done with passes=false)." >&2 + jq -r '.stories[] | select((((.status // "open") | ascii_downcase) == "done") and (.passes != true)) | "- \(.id // "(unknown)")"' "$PRD_PATH" >&2 + exit 1 + fi + + remaining="$(jq '[.stories[] | select(.passes != true)] | length' "$PRD_PATH")" + if [ "$remaining" -eq 0 ]; then + echo "No remaining stories with passes=false. Stopping loop." + break + fi + + ts="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" + log_file="$LOG_DIR/loop-${ts//:/-}.log" + + echo "Running Ralph iteration (remaining stories: $remaining)" + if ! ralph build 1 --prd .agents/tasks/prd-gitcontext.json --agent=claude --no-commit 2>&1 | tee "$log_file"; then + echo "Ralph iteration failed. See $log_file" >&2 + exit 1 + fi + + git add -A + if git diff --cached --quiet; then + echo "No changes detected; skipping commit." + else + if ! git commit -m "chore(loop): checkpoint $ts"; then + echo "Checkpoint commit failed; fix git identity/hook errors, then rerun loop." >&2 + exit 1 + fi + fi +done diff --git a/.codex/ralph-gitcontext/verify/.gitkeep b/.codex/ralph-gitcontext/verify/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/.codex/ralph-gitcontext/verify/app-copy-output-tests.md b/.codex/ralph-gitcontext/verify/app-copy-output-tests.md new file mode 100644 index 0000000..1feff23 --- /dev/null +++ b/.codex/ralph-gitcontext/verify/app-copy-output-tests.md @@ -0,0 +1,74 @@ +# Verification: app-copy-output-tests + +## Story: Frontend: copy output generation tests + +### Pass Criteria + +- [x] At least 8 tests covering: header, instructions, file tree toggle, modify/add/remove diffs, binary handling, context lines + - 26 tests total across 6 describe blocks covering all required areas +- [x] Output format matches exact markdown structure used by the app + - End-to-end test verifies section ordering (header -> instructions -> file tree -> diffs -> file sections) + - File section format verified: `## FILE: path (STATUS)` with appropriate code blocks +- [x] Binary file handling verified for both includeBinaryAsPaths settings + - Extension-based binary detection (e.g., .png) produces [Binary file] placeholder + - Content-flag binary detection (binary: true from ReadFileSide) produces [Binary file] placeholder + - Non-binary extensions (e.g., .dat) are correctly treated as text +- [x] All tests pass with `npm --workspace apps/desktop run test` + - 252 tests pass (26 new copyOutput tests + 226 existing) + +### Test Breakdown (26 tests) + +**buildHeader** (6 tests): +1. Header includes repo path, base/compare branches, and file count +2. Instructions section included when non-empty +3. Instructions section omitted when empty/whitespace +4. File tree section included when fileTreeText provided +5. File tree section omitted when fileTreeText empty +6. Shows "Unknown" when currentDir is empty + +**generateFileTreeText** (3 tests): +7. Generates tree for selected files only +8. Excludes directories without selected files +9. Wraps output in code fences + +**buildFileSection** (9 tests): +10. Modified files produce ```diff blocks +11. Added files with unlimited context produce ``` blocks (full content) +12. Added files with limited context produce ```diff blocks +13. Removed files produce diff blocks +14. Binary files by extension show [Binary file] placeholder +15. Binary files by content flag show [Binary file] placeholder +16. Unchanged files produce full content blocks +17. Unchanged file with notFound returns empty string +18. Context lines setting affects diff output length + +**buildCopyOutput** (4 tests): +19. Multiple file sections concatenated in order +20. File tree included when includeFileTree is true +21. File tree omitted when includeFileTree is false +22. Instructions included when non-empty + +**Binary file handling** (3 tests): +23. Binary files by extension produce placeholder with path +24. Binary files by content flag produce placeholder +25. Non-binary .dat extension treated as text + +**End-to-end format** (1 test): +26. Full output structure ordering verified for realistic scenario + +### Quality Gates + +| Gate | Result | +|------|--------| +| `npm --workspace apps/desktop run test` | PASS (252 tests) | +| `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml` | PASS (44 tests) | +| `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings` | PASS | +| `npm run web:build` | PASS | + +### Files Changed + +- `apps/desktop/src/utils/copyOutput.ts` (new) - extracted testable output-building functions +- `apps/desktop/src/__tests__/copyOutput.test.ts` (new) - 26 comprehensive tests +- `apps/desktop/src/App.tsx` (modified) - refactored to use extracted functions + +VERIFIED: YES diff --git a/.codex/ralph-gitcontext/verify/clipboard-batch-select-comprehensive.md b/.codex/ralph-gitcontext/verify/clipboard-batch-select-comprehensive.md new file mode 100644 index 0000000..689a5ac --- /dev/null +++ b/.codex/ralph-gitcontext/verify/clipboard-batch-select-comprehensive.md @@ -0,0 +1,44 @@ +# Verification Report: clipboard-batch-select-comprehensive + +## Story: Frontend: clipboardBatchSelect comprehensive tests + +### Pass Criteria + +- [x] At least 10 total tests (including existing 5) covering Windows paths, UNC, duplicates, special chars, relative paths + - **Result**: 19 total tests (5 existing + 14 new) +- [x] Cross-platform path handling verified + - **Result**: Tests cover Unix paths, Windows drive letter paths (C:\), UNC paths (\\server\share), case-insensitive matching, trailing slashes, and mixed separators +- [x] All tests pass with `npm --workspace apps/desktop run test` + - **Result**: 171 tests passed (19 clipboardBatchSelect tests) + +### Bug Fix + +Fixed trailing slash bug in `normalizeClipboardPath` (`clipboardBatchSelect.ts:55`): +- Input paths with trailing slashes (e.g., `src/dir/`) were not stripped, producing inconsistent results +- Added `stripTrailingSlashes()` call on the normalized input line (repo root was already stripped) + +### Tests Added (14 new) + +1. Normalizes Windows drive letter absolute path to relative +2. Handles case-insensitive matching for Windows drive letter paths +3. Returns null for Windows drive letter path outside repo +4. Normalizes UNC paths correctly +5. Returns null for UNC path outside repo root +6. Strips trailing slashes from repo root and paths +7. Filters whitespace-only lines in parseClipboardPathLines +8. Deduplicates matched paths in resolveSelectablePaths +9. Handles paths with spaces and unicode characters +10. Resolves relative paths with ./ prefix +11. Resolves relative paths with multiple ./ segments stripped +12. Counts paths outside repo root correctly in resolveSelectablePaths +13. Returns null when path equals repo root exactly +14. Resolves Windows drive letter paths end-to-end via resolveSelectablePaths + +### Quality Gates + +- `npm --workspace apps/desktop run test` -> PASS (171 tests) +- `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml` -> PASS (44 tests) +- `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings` -> PASS +- `npm run web:build` -> PASS + +VERIFIED: YES diff --git a/.codex/ralph-gitcontext/verify/concurrency-util-tests.md b/.codex/ralph-gitcontext/verify/concurrency-util-tests.md new file mode 100644 index 0000000..1303f3d --- /dev/null +++ b/.codex/ralph-gitcontext/verify/concurrency-util-tests.md @@ -0,0 +1,45 @@ +# Verification Report: concurrency-util-tests + +## Story: Frontend: concurrency utility tests + +### Test Results + +- `npm --workspace apps/desktop run test` -> PASS (151 tests, 10 new concurrency tests) +- `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml` -> PASS (44 tests) +- `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings` -> PASS +- `npm run web:build` -> PASS + +### Acceptance Criteria + +- [x] At least 5 tests covering: concurrency limit, result ordering, error propagation, empty input, sequential (limit=1) + - 10 tests total: 7 for mapWithConcurrency, 3 for createConcurrencyLimiter +- [x] Concurrency limit verified by tracking in-flight count during execution + - Tests track `inFlight` counter incremented before async work and decremented after, recording `maxInFlight` + - Verified maxInFlight <= limit for both mapWithConcurrency (limit=2) and createConcurrencyLimiter (limit=2) +- [x] Result ordering matches input ordering regardless of completion order + - mapWithConcurrency test uses items with varying delays (50ms, 10ms, 30ms, 20ms, 40ms) to ensure different completion order + - Results verified to match input order: ['result-50', 'result-10', 'result-30', 'result-20', 'result-40'] + - createConcurrencyLimiter test verifies Promise.all preserves order with slow/fast/medium tasks +- [x] All tests pass with `npm --workspace apps/desktop run test` + - 151 total tests pass across 11 test files + +### Tests Implemented + +**mapWithConcurrency (7 tests):** +1. respects concurrency limit by never exceeding max in-flight count +2. processes all items and returns results in input order +3. propagates errors from rejected promises +4. returns empty array for empty input +5. processes sequentially when limit is 1 +6. works correctly when limit is greater than array length +7. supports cancellation via AbortSignal + +**createConcurrencyLimiter (3 tests):** +1. limits concurrent execution to the specified count +2. returns results in correct order from wrapped functions +3. propagates errors from limited functions + +### Files Changed +- `apps/desktop/src/utils/concurrency.test.ts` (new, 194 lines) + +VERIFIED: YES diff --git a/.codex/ralph-gitcontext/verify/context-footer-tests.md b/.codex/ralph-gitcontext/verify/context-footer-tests.md new file mode 100644 index 0000000..9b8a51c --- /dev/null +++ b/.codex/ralph-gitcontext/verify/context-footer-tests.md @@ -0,0 +1,38 @@ +# Verification Report: context-footer-tests + +## Story: Frontend: ContextFooter component tests + +### Pass Criteria + +- [x] At least 7 tests covering: token breakdown display, progress bar, over-limit warning, disabled state, copy callback, flash messages +- [x] Over-limit correctly detected when total > limit +- [x] All tests pass with `npm --workspace apps/desktop run test` + +### Test Summary + +15 tests created in `apps/desktop/src/components/ContextFooter.test.tsx`: + +1. **displays correct token breakdown** - Verifies files count, file content, instructions, file tree, and total token display +2. **progress bar width matches percentage** - 50% fill width for 64000/128000 tokens +3. **progress bar capped at 100%** - Width is 100% when total exceeds limit +4. **over-limit state shows warning styling** - `.over-limit` class on token-value and progress fill when total > limit +5. **over-limit not applied when total equals limit** - Boundary check: totalTokens === limit is NOT over-limit +6. **copy button disabled when no files selected** - filesCount=0 disables button +7. **copy button disabled when disabled prop** - disabled=true disables button +8. **copy button enabled when files selected** - Normal state with correct label text +9. **clicking copy calls onCopy** - User event click triggers callback +10. **copy flash success message** - "Copied!" replaces button text, button disabled during flash +11. **copy flash failure message** - "Copy failed" replaces button text, button disabled +12. **zero limit hides progress bar** - limit=0 removes `.token-progress-bar` from DOM +13. **positive limit shows progress bar** - limit>0 renders `.token-progress-bar` +14. **token counts formatted with commas** - Large numbers (1,234,567) formatted with toLocaleString() +15. **shows calculating text when busy** - "calculating..." shown when context busy=true + +### Verification Commands + +- `npm --workspace apps/desktop run test` -> PASS (199 tests, 15 new in ContextFooter.test.tsx) +- `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml` -> PASS (44 tests) +- `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings` -> PASS +- `npm run web:build` -> PASS + +VERIFIED: YES diff --git a/.codex/ralph-gitcontext/verify/debounce-util-tests.md b/.codex/ralph-gitcontext/verify/debounce-util-tests.md new file mode 100644 index 0000000..0fa10e0 --- /dev/null +++ b/.codex/ralph-gitcontext/verify/debounce-util-tests.md @@ -0,0 +1,39 @@ +# Verification Report: debounce-util-tests + +## Story +**ID:** debounce-util-tests +**Title:** Frontend: debounce utility tests + +## Pass Criteria + +- [x] At least 4 tests covering: single invocation after burst, correct arguments, timer reset, delay expiration +- [x] Uses vi.useFakeTimers for reliable timing +- [x] All tests pass with `npm --workspace apps/desktop run test` + +## Verification Commands + +| Command | Result | +|---------|--------| +| `npm --workspace apps/desktop run test` | PASS (157 tests, 6 new debounce tests) | +| `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml` | PASS (44 tests) | +| `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings` | PASS | +| `npm run web:build` | PASS | + +## Tests Created (6 total) + +1. **only invokes once after a burst of calls within the delay** - 5 rapid calls result in exactly 1 invocation after delay +2. **uses the arguments from the final call** - 'third' argument used, not 'first' or 'second' +3. **resets the timer on each call so rapid calls delay execution** - calls at 80ms intervals reset 100ms timer +4. **triggers the function after the full delay elapses** - fires at exactly 300ms, not at 299ms +5. **returned function is callable multiple independent times** - two separate cycles each invoke correctly +6. **handles multiple arguments correctly** - (number, string, object) args passed through + +## Files Changed + +- `apps/desktop/src/utils/debounce.test.ts` (new) + +## Unresolved Risks + +None. + +VERIFIED: YES diff --git a/.codex/ralph-gitcontext/verify/diff-control-bar-comprehensive.md b/.codex/ralph-gitcontext/verify/diff-control-bar-comprehensive.md new file mode 100644 index 0000000..69c5e90 --- /dev/null +++ b/.codex/ralph-gitcontext/verify/diff-control-bar-comprehensive.md @@ -0,0 +1,44 @@ +# Verification Report: diff-control-bar-comprehensive + +## Story: Frontend: DiffControlBar comprehensive interaction tests + +### Pass Criteria + +- [x] At least 28 total tests (including existing 23) covering additional interaction edge cases + - Result: 32 total tests (19 existing + 13 new) +- [x] WORKDIR display text verified + - Test: `__WORKDIR__ displays as My Working Directory in base branch selector` + - Test: `displays "My Working Directory" for __WORKDIR__ branch` (existing) +- [x] Disabled state comprehensively tested + - Test: `disables all controls when disabled prop is true` (existing) + - Test: `all controls are enabled when disabled prop is false` + - Test: `disabled prop overrides individual button enable conditions` + - Test: `delete button is disabled when no workspace is selected` + - Test: `save button is disabled when currentWorkspacePath is empty` +- [x] All tests pass with `npm --workspace apps/desktop run test` + - Result: 307 tests pass across 21 test files (0 failures) + +### New Tests Added (13) + +1. `delete button is disabled when no workspace is selected` +2. `delete button is enabled when a workspace is selected` +3. `save button is disabled when currentWorkspacePath is empty` +4. `workspace selector shows unsaved label with folder name when no workspace selected` +5. `workspace selector shows "Unsaved Workspace" when no path provided and no workspace selected` +6. `workspace options display name and folder name` +7. `selecting empty workspace ID calls onWorkspaceSelect with empty string` +8. `workspace selector shows fallback text for unknown workspace ID` +9. `workspace selector has title attribute showing workspace path` +10. `workspace selector title shows selected workspace path when workspace is selected` +11. `__WORKDIR__ displays as My Working Directory in base branch selector` +12. `all controls are enabled when disabled prop is false` +13. `disabled prop overrides individual button enable conditions` + +### Quality Gates + +- `npm --workspace apps/desktop run test` -> PASS (307 tests, 21 files) +- `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml` -> PASS (44 tests) +- `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings` -> PASS +- `npm run web:build` -> PASS + +VERIFIED: YES diff --git a/.codex/ralph-gitcontext/verify/diff-util-tests.md b/.codex/ralph-gitcontext/verify/diff-util-tests.md new file mode 100644 index 0000000..d587170 --- /dev/null +++ b/.codex/ralph-gitcontext/verify/diff-util-tests.md @@ -0,0 +1,49 @@ +# Verification Report: diff-util-tests + +## Story +- **ID**: diff-util-tests +- **Title**: Frontend: diff utility tests + +## Commands Run +| Command | Result | +|---------|--------| +| `npm --workspace apps/desktop run test` | PASS (129 tests, 25 new in diff.test.ts) | +| `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml` | PASS (44 tests) | +| `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings` | PASS | +| `npm run web:build` | PASS | + +## Files Changed +- `apps/desktop/src/utils/diff.test.ts` (new, 25 tests) + +## Acceptance Criteria Verification + +### At least 8 tests covering: modify, add, remove, unchanged, CRLF, context lines, empty files, no trailing newline +- PASS: 25 tests total covering all required scenarios: + - Modify: `produces correct unified diff for modified file with context lines`, `produces diff for modified file` + - Add: `shows all lines as additions for added file`, `produces diff for added file` + - Remove: `shows all lines as deletions for removed file`, `produces diff for removed file` + - Unchanged: `returns empty diff body when old and new are identical`, `returns full text for unchanged file` + - CRLF: `normalizes CRLF to LF for stable diffs`, `produces stable diff when both sides have CRLF`, `handles CRLF content through buildUnifiedDiffForStatus` + - Context lines: `context=0 shows only changed lines`, `context=3 shows 3 lines around changes`, `context=999 shows all lines in the file`, `passes context option through to createUnifiedDiffForPath` + - Empty files: `handles empty files producing valid diff` + - No trailing newline: `handles file with no trailing newline` + +### Context lines verified: setting 0 shows only changed lines, 3 shows 3 lines around changes, 999 shows full file +- PASS: Three dedicated tests verify each context setting: + - context=0: Verifies no context lines appear in the hunk (only +/- lines) + - context=3: Verifies exactly 3 lines before and after the change, and lines beyond 3 are excluded + - context=999: Verifies all lines from a 10-line file appear in the diff output + +### CRLF normalization verified with mixed line endings +- PASS: Three tests cover CRLF handling: + - Mixed: `\r\n` old + `\n` new produces clean diff with only actual content changes + - Both CRLF: identical content with `\r\n` on both sides produces no hunks + - Via buildUnifiedDiffForStatus: CRLF content passed through status wrapper is normalized + +### All tests pass with `npm --workspace apps/desktop run test` +- PASS: All 129 tests pass (25 new diff.test.ts tests + 104 existing tests) + +## Unresolved Risks / Follow-ups +- None identified + +VERIFIED: YES diff --git a/.codex/ralph-gitcontext/verify/error-handling-tests.md b/.codex/ralph-gitcontext/verify/error-handling-tests.md new file mode 100644 index 0000000..6a0c8db --- /dev/null +++ b/.codex/ralph-gitcontext/verify/error-handling-tests.md @@ -0,0 +1,54 @@ +# Verification Report: error-handling-tests + +## Story +**ID:** error-handling-tests +**Title:** Frontend: global error handling and ErrorBanner integration tests + +## Acceptance Criteria Verification + +- [x] At least 12 total tests (including existing 7) covering error propagation, auto-clear, dismiss, recovery flow + - **Result:** 21 total tests (8 existing unit tests + 13 new integration tests) +- [x] Error messages from different sources verified (repo load, diff compute, unhandled rejection) + - **Result:** Tests cover: repo load error, diff compute error, unhandled promise rejection (Error + non-Error), window.error event (with/without error object) +- [x] All tests pass with `npm --workspace apps/desktop run test` + - **Result:** PASS — 278 tests across 19 test files, 0 failures + +## Commands Run + +| Command | Result | +|---------|--------| +| `npm --workspace apps/desktop run test` | PASS (278 tests, 19 files) | +| `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml` | PASS (44 tests) | +| `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings` | PASS | +| `npm run web:build` | PASS | + +## Files Changed + +- `apps/desktop/src/components/ErrorBanner.test.tsx` — Added 13 integration tests + +## Tests Added + +### Integration tests (ErrorBanner integration describe block): +1. Unhandled promise rejection displays in error banner +2. Unhandled promise rejection with non-Error reason displays stringified value +3. Window.error event displays in error banner +4. Window.error event without error object uses message field +5. Error banner auto-clears when new repo loads successfully +6. Multiple rapid errors show only the latest +7. Error from loadRepoFromHandle propagates to banner +8. Error from computeDiffAndTree propagates to banner +9. Dismissing error clears it and does not reappear unless a new error occurs +10. Error recovery flow: error state → user action → success state +11. Re-renders correctly when error message changes +12. Transitions from error to null hides the banner +13. Global listeners are cleaned up on unmount + +## Unresolved Risks + +None identified. + +## Commit + +`89cd1ab test(desktop): add integration tests for ErrorBanner error handling` + +VERIFIED: YES diff --git a/.codex/ralph-gitcontext/verify/logger-util-tests.md b/.codex/ralph-gitcontext/verify/logger-util-tests.md new file mode 100644 index 0000000..aa2162a --- /dev/null +++ b/.codex/ralph-gitcontext/verify/logger-util-tests.md @@ -0,0 +1,36 @@ +# Verification: logger-util-tests + +## Story +**ID:** logger-util-tests +**Title:** Frontend: logger utility tests + +## Acceptance Criteria + +- [x] At least 4 tests covering: Error objects, strings, other types, error code formatting +- [x] console.error mock verifies exact output format +- [x] All tests pass with `npm --workspace apps/desktop run test` + +## Test Summary + +6 tests created in `apps/desktop/src/utils/logger.test.ts`: + +1. **calls console.error with formatted source prefix and the Error object** — verifies `console.error('[GIT_DIFF]', err)` called with Error object +2. **extracts message from Error objects and pushes to errorLog** — verifies `.message` extraction and errorLog entry shape +3. **handles string errors by using the string directly as the message** — verifies string passthrough +4. **handles non-Error non-string values (objects, null, undefined)** — verifies `String()` conversion for objects → '[object Object]', null → 'null', undefined → 'undefined' +5. **includes the error code/source prefix in console.error output** — verifies `[E_REPO_OPEN]` prefix format +6. **caps errorLog at 100 entries by removing oldest** — verifies FIFO eviction when exceeding 100 entries + +## Quality Gates + +| Command | Result | +|---------|--------| +| `npm --workspace apps/desktop run test` | PASS (294 tests, 21 files) | +| `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml` | PASS (44 tests) | +| `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings` | PASS | +| `npm run web:build` | PASS | + +## Commit +b3abfde test(desktop): add logger utility unit tests + +VERIFIED: YES diff --git a/.codex/ralph-gitcontext/verify/models-util-tests.md b/.codex/ralph-gitcontext/verify/models-util-tests.md new file mode 100644 index 0000000..967e138 --- /dev/null +++ b/.codex/ralph-gitcontext/verify/models-util-tests.md @@ -0,0 +1,55 @@ +# Verification Report: models-util-tests + +## Story +**ID:** models-util-tests +**Title:** Frontend: models utility tests + +## Acceptance Criteria + +- [x] At least 5 tests covering: model list structure, required fields, unique IDs, persistence, invalid saved model +- [x] ModelInfo type compliance verified +- [x] All tests pass with `npm --workspace apps/desktop run test` + +## Test Summary + +13 tests in `apps/desktop/src/utils/models.test.ts`: + +**getModels (8 tests):** +1. returns an array of ModelInfo objects from a fresh API fetch +2. each model has required fields: id, name, context_length +3. model IDs are unique +4. context_length values are positive numbers +5. model list is not empty +6. returns cached models when cache is fresh (< 1 day) +7. refetches when cache is stale (> 1 day) +8. returns null when API fails and no cache exists + +**model selection persistence (5 tests):** +1. selectedModel is saved to localStorage key gc.selectedModel +2. restoring selected model from localStorage works +3. invalid saved model ID is handled gracefully +4. empty localStorage returns empty string for selected model +5. ModelInfo type compliance: objects satisfy the type contract + +## Verification Commands + +| Command | Result | +|---------|--------| +| `npm --workspace apps/desktop run test` | PASS (265 tests, 19 files) | +| `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml` | PASS (44 tests) | +| `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings` | PASS | +| `npm run web:build` | PASS | + +## Files Changed + +- `apps/desktop/src/utils/models.test.ts` (new, 257 lines) + +## Commit + +`f4976ab test(desktop): add comprehensive Vitest tests for models utility` + +## Risks / Follow-ups + +- None identified. Tests are self-contained with proper mocking of fetch and localStorage. + +VERIFIED: YES diff --git a/.codex/ralph-gitcontext/verify/right-panel-tabs-tests.md b/.codex/ralph-gitcontext/verify/right-panel-tabs-tests.md new file mode 100644 index 0000000..ae63917 --- /dev/null +++ b/.codex/ralph-gitcontext/verify/right-panel-tabs-tests.md @@ -0,0 +1,34 @@ +# Verification: right-panel-tabs-tests + +## Story +**ID:** right-panel-tabs-tests +**Title:** Frontend: RightPanelTabs component tests + +## Commands Run +- `npm --workspace apps/desktop run test` -> PASS (207 tests, 15 test files, all passed) +- `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml` -> PASS (44 passed) +- `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings` -> PASS (clean) +- `npm run web:build` -> PASS (built successfully) + +## Pass Criteria +- At least 5 tests covering: tab rendering, badge count, tab switching, active styling, children rendering -> **PASS** (8 tests) +- Tab IDs 'files' and 'settings' verified -> **PASS** (tests verify onTabChange called with 'files' and 'settings') +- All tests pass with `npm --workspace apps/desktop run test` -> **PASS** + +## Tests Created (8 total) +1. renders two tabs: Selected Files and Settings +2. files tab shows badge with file count when filesCount > 0 +3. badge is not shown when filesCount is 0 +4. clicking files tab calls onTabChange with "files" +5. clicking settings tab calls onTabChange with "settings" +6. active tab has "active" class styling +7. children content renders in tab panel +8. switching tabs preserves children (no remount) + +## Files Changed +- `apps/desktop/src/components/RightPanelTabs.test.tsx` (created) + +## Risks / Follow-ups +- None identified + +VERIFIED: YES diff --git a/.codex/ralph-gitcontext/verify/rust-git-diff.md b/.codex/ralph-gitcontext/verify/rust-git-diff.md new file mode 100644 index 0000000..f5330fe --- /dev/null +++ b/.codex/ralph-gitcontext/verify/rust-git-diff.md @@ -0,0 +1,51 @@ +# Verification Report: rust-git-diff + +## Story +**ID:** rust-git-diff +**Title:** Rust backend: git_diff command tests + +## Acceptance Criteria + +- [x] At least 7 unit tests covering: identical branches, add, modify, remove, rename, WORKDIR sentinel, invalid branch + - 8 git_diff tests + 1 pre-existing open_repo test = 9 total +- [x] Rename detection verified via old_path field in DiffFile + - `test_diff_detects_renamed_files` asserts `old_path == Some("README.md")` +- [x] WORKDIR test creates actual uncommitted changes in temp repo + - `test_diff_workdir_detects_uncommitted_changes` modifies README.md without committing, then diffs against `__WORKDIR__` +- [x] All tests pass with `cargo test` + +## Commands Run + +| Command | Result | +|---------|--------| +| `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml` | PASS (9/9 tests) | +| `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings` | PASS | +| `cargo fmt --check --manifest-path apps/desktop/src-tauri/Cargo.toml` | PASS | +| `npm run web:build` | PASS | + +## Tests Added (8 new git_diff tests) + +1. `test_diff_identical_branches_returns_empty` - diff main vs main returns empty files array +2. `test_diff_detects_added_files` - new file on feature branch shows as "add" +3. `test_diff_detects_modified_files` - modified file on feature branch shows as "modify" +4. `test_diff_detects_removed_files` - deleted file on feature branch shows as "remove" +5. `test_diff_detects_renamed_files` - renamed file detected via find_similar, old_path populated +6. `test_diff_workdir_detects_uncommitted_changes` - `__WORKDIR__` sentinel compares branch to working directory +7. `test_diff_nonexistent_branch_returns_error` - invalid branch returns descriptive error +8. `test_diff_binary_files_detected` - binary file appears in diff and read_file_blob marks it binary + +## Test Helper + +- `create_test_repo()` - creates a temp git repo with initial commit on "main", returns `(TempDir, String)` + +## Files Changed + +- `apps/desktop/src-tauri/Cargo.toml` - added `tempfile = "3"` dev-dependency +- `apps/desktop/src-tauri/src/git.rs` - replaced minimal test module with comprehensive tests; fixed pre-existing clippy warnings (contains, needless_return, useless_format); formatted with cargo fmt +- `apps/desktop/src-tauri/src/lib.rs` - formatted with cargo fmt (whitespace only) + +## Unresolved Risks + +- None + +VERIFIED: YES diff --git a/.codex/ralph-gitcontext/verify/rust-git-list-files.md b/.codex/ralph-gitcontext/verify/rust-git-list-files.md new file mode 100644 index 0000000..e3da87d --- /dev/null +++ b/.codex/ralph-gitcontext/verify/rust-git-list-files.md @@ -0,0 +1,38 @@ +# Verification: rust-git-list-files + +## Story +Rust backend: list_files and list_files_with_oids tests + +## Pass Criteria + +- [x] At least 6 tests covering list_files, list_files_with_oids, and resolve_ref +- [x] WORKDIR listing respects .gitignore patterns +- [x] OIDs are valid 40-char hex strings +- [x] All tests pass with `cargo test` + +## Tests Added (11 total) + +### list_files (6 tests) +1. `test_list_files_returns_all_committed_files` - listing files for a ref returns all committed files +2. `test_list_files_nested_directories_flatten_to_full_paths` - nested dirs flatten to full paths (src/lib/util.rs) +3. `test_list_files_empty_tree_returns_empty_list` - empty tree returns empty list +4. `test_list_files_workdir_returns_filesystem_files` - WORKDIR returns filesystem files including untracked +5. `test_list_files_workdir_respects_gitignore` - WORKDIR listing respects .gitignore patterns (*.log, build/) + +### list_files_with_oids (2 tests) +6. `test_list_files_with_oids_returns_valid_oid_strings` - OIDs are valid 40-char hex strings +7. `test_list_files_with_oids_multiple_files` - multiple files each have valid unique OIDs + +### resolve_ref (3 tests) +8. `test_resolve_ref_valid_branch_resolves_to_oid` - valid branch resolves to 40-char hex OID +9. `test_resolve_ref_invalid_ref_returns_error` - invalid ref returns error +10. `test_resolve_ref_head_resolves_correctly` - HEAD resolves to same OID as main + +## Quality Gates + +- `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml` -> PASS (28 tests passed) +- `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings` -> PASS +- `npm --workspace apps/desktop run test` -> PASS (45 tests passed) +- `npm run web:build` -> PASS + +VERIFIED: YES diff --git a/.codex/ralph-gitcontext/verify/rust-git-read-file.md b/.codex/ralph-gitcontext/verify/rust-git-read-file.md new file mode 100644 index 0000000..c5dd1b3 --- /dev/null +++ b/.codex/ralph-gitcontext/verify/rust-git-read-file.md @@ -0,0 +1,48 @@ +# Verification Report: rust-git-read-file + +## Story +**ID**: rust-git-read-file +**Title**: Rust backend: read_file_blob command tests + +## Acceptance Criteria Verification + +- [x] At least 6 tests covering: text file, missing file, binary file, WORKDIR read, non-UTF8, bad ref + - 9 tests added: text file, missing file, binary file, WORKDIR read, WORKDIR missing, non-UTF8 (git blob), non-UTF8 (WORKDIR), bad ref, WORKDIR binary +- [x] Binary detection verified (null byte in content) + - `test_read_file_blob_binary_file_returns_binary_true` commits a file with null bytes and verifies `binary=true` + - `test_read_file_blob_workdir_binary_file_detected` writes binary to workdir and verifies detection +- [x] Lossy UTF-8 conversion produces replacement characters for invalid bytes + - `test_read_file_blob_non_utf8_lossy_conversion` uses invalid UTF-8 bytes (0xC0, 0xC1) and asserts U+FFFD presence + - `test_read_file_blob_non_utf8_workdir_lossy_conversion` does the same for WORKDIR path +- [x] All tests pass with `cargo test` + +## Commands Run + +| Command | Result | +|---------|--------| +| `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml` | PASS (18 passed, 0 failed) | +| `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings` | PASS | +| `cargo fmt --check --manifest-path apps/desktop/src-tauri/Cargo.toml` | PASS | +| `npm run web:build` | PASS | + +## Tests Added (9 tests in `apps/desktop/src-tauri/src/git.rs`) + +1. `test_read_file_blob_text_file_returns_correct_content` - reads committed text file, verifies exact content +2. `test_read_file_blob_missing_file_returns_not_found` - non-existent file returns notFound=true +3. `test_read_file_blob_binary_file_returns_binary_true` - file with null bytes returns binary=true +4. `test_read_file_blob_workdir_reads_from_filesystem` - __WORKDIR__ reads uncommitted file from disk +5. `test_read_file_blob_workdir_missing_file_returns_not_found` - missing workdir file returns notFound=true +6. `test_read_file_blob_non_utf8_lossy_conversion` - invalid UTF-8 in git blob produces U+FFFD replacement chars +7. `test_read_file_blob_non_utf8_workdir_lossy_conversion` - invalid UTF-8 in workdir produces U+FFFD replacement chars +8. `test_read_file_blob_bad_ref_returns_error` - non-existent ref returns descriptive error +9. `test_read_file_blob_workdir_binary_file_detected` - binary file in workdir detected via null bytes + +## Files Changed + +- `apps/desktop/src-tauri/src/git.rs` - added 9 read_file_blob tests + +## Risks / Follow-ups + +None identified. Tests are isolated using temp directories and do not affect production code. + +VERIFIED: YES diff --git a/.codex/ralph-gitcontext/verify/rust-watcher.md b/.codex/ralph-gitcontext/verify/rust-watcher.md new file mode 100644 index 0000000..570d6a6 --- /dev/null +++ b/.codex/ralph-gitcontext/verify/rust-watcher.md @@ -0,0 +1,34 @@ +# Verification Report: rust-watcher + +## Story +**ID:** rust-watcher +**Title:** Rust backend: file watcher tests + +## Changes +- **apps/desktop/src-tauri/src/watcher.rs**: Extracted path-filtering logic from the watcher callback into a pure `classify_path` function and `EventKind` enum. Added 17 comprehensive unit tests in a `#[cfg(test)]` module. Refactored the `RepoWatcher::new` closure to use `classify_path`. +- **apps/desktop/src-tauri/src/git.rs**: Auto-formatted by `cargo fmt` (pre-existing formatting issues from earlier stories). + +## Acceptance Criteria Verification + +- [x] Path filtering logic is tested: node_modules filtered, .git/objects filtered, .git/logs filtered, target/ filtered + - Tests: `ignores_node_modules`, `ignores_nested_node_modules`, `ignores_git_objects`, `ignores_git_logs`, `ignores_target_directory_at_root`, `ignores_nested_target_directory` +- [x] Regular file paths pass the filter + - Tests: `detects_regular_source_file`, `detects_root_level_file`, `detects_deeply_nested_file`, `file_named_target_not_ignored` +- [x] .git/refs and .git/HEAD paths trigger refs-changed classification + - Tests: `detects_git_refs_heads_change`, `detects_git_refs_tags_change`, `detects_git_refs_remotes_change`, `detects_git_head_change` +- [x] All tests pass with `cargo test` + +## Commands Run + +| Command | Result | +|---------|--------| +| `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml` | PASS (44 tests) | +| `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings` | PASS | +| `cargo fmt --check --manifest-path apps/desktop/src-tauri/Cargo.toml` | PASS | +| `npm --workspace apps/desktop run test` | PASS (45 tests) | +| `npm run web:build` | PASS | + +## Risks / Follow-ups +- None. The refactoring is behavior-preserving and all quality gates pass. + +VERIFIED: YES diff --git a/.codex/ralph-gitcontext/verify/selected-files-panel-tests.md b/.codex/ralph-gitcontext/verify/selected-files-panel-tests.md new file mode 100644 index 0000000..034526a --- /dev/null +++ b/.codex/ralph-gitcontext/verify/selected-files-panel-tests.md @@ -0,0 +1,41 @@ +# Verification: selected-files-panel-tests + +## Story: Frontend: SelectedFilesPanel component tests + +## Pass Criteria + +- [x] At least 7 tests covering: rendering, sort modes, binary files, remove/reveal/preview callbacks, empty state +- [x] Sorting verified by checking DOM order after sort change +- [x] TokenCountsContext properly mocked +- [x] All tests pass with `npm --workspace apps/desktop run test` + +## Test Summary + +13 tests created in `apps/desktop/src/components/SelectedFilesPanel.test.tsx`: + +1. **renders list of selected files with correct status icons** - Verifies modify/add/remove/unchanged status icons via aria-labels +2. **sorts by tokens high-to-low by default** - Checks DOM order matches descending token count +3. **sorts by tokens low-to-high when sort mode is changed** - Changes select to tokens-asc, verifies DOM reorder +4. **sorts by name A-Z and Z-A when sort mode is changed** - Tests both name-asc and name-desc, verifies DOM order +5. **binary files show binary indicator and preview button is disabled** - Checks .png file has Binary file label and disabled preview button +6. **clicking remove calls onUnselect with correct path** - Clicks remove button, verifies callback with correct path +7. **clicking reveal calls onReveal with correct path** - Clicks reveal button, verifies callback with correct path +8. **clicking preview calls onPreview with path and status** - Clicks preview button, verifies callback with path and status +9. **empty selection shows empty state message** - Renders with empty Set, checks "No Files Selected" text +10. **refreshing state shows recalculating indicator** - Renders with refreshing=true, checks "Recalculating..." text +11. **busy context state shows recalculating indicator** - Sets mock busy=true, checks indicator +12. **reveal button not rendered when onReveal is not provided** - Verifies conditional rendering +13. **displays token counts formatted with locale string** - Verifies 12345 renders as "12,345" + +## Quality Gates + +- `npm --workspace apps/desktop run test`: PASS (184 tests, 13 files) +- `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml`: PASS (44 tests) +- `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings`: PASS +- `npm run web:build`: PASS + +## Commit + +6f6a5e3 test(desktop): add comprehensive Vitest tests for SelectedFilesPanel + +VERIFIED: YES diff --git a/.codex/ralph-gitcontext/verify/tauri-git-service.md b/.codex/ralph-gitcontext/verify/tauri-git-service.md new file mode 100644 index 0000000..5186723 --- /dev/null +++ b/.codex/ralph-gitcontext/verify/tauri-git-service.md @@ -0,0 +1,47 @@ +# Verification Report: tauri-git-service + +**Story:** Frontend: TauriGitService unit tests +**Date:** 2026-02-22 +**Run:** 20260222-104122-$ (iteration 5) + +## Pass Criteria + +- [x] At least 8 tests covering all GitService interface methods plus dispose and singleton + - 18 tests total: loadRepo (1), listBranches (2), getDiff (2), readFile (3), listFiles (1), listFilesWithOids (1), resolveRef (1), dispose (3), error propagation (2), singleton/reuse (2) +- [x] All invoke calls verified with correct command names and argument shapes + - open_repo, get_branches, git_diff, read_file_blob, list_files, list_files_with_oids, resolve_ref, close_repo - all verified +- [x] Error propagation tested for at least one method + - Tested for loadRepo (invoke rejection) and getDiff (invoke rejection) +- [x] All tests pass with `npm --workspace apps/desktop run test` + +## Commands Run + +| Command | Result | +|---------|--------| +| `npm --workspace apps/desktop run test` | PASS (63 tests, 18 new) | +| `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml` | PASS (44 tests) | +| `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings` | PASS | +| `npm run web:build` | PASS | + +## Files Changed + +- `apps/desktop/src/services/TauriGitService.test.ts` (new) + +## Test Coverage Details + +1. **loadRepo** - calls invoke('open_repo') with correct path, returns mapped result +2. **listBranches** - calls invoke('get_branches') with repo path; throws when no repo loaded +3. **getDiff** - calls invoke('git_diff') with base/compare; throws when no repo loaded +4. **readFile** - maps not_found to notFound; handles missing files; handles binary files +5. **listFiles** - returns flat array via invoke('list_files') +6. **listFilesWithOids** - returns files with OIDs via invoke('list_files_with_oids') +7. **resolveRef** - returns OID string via invoke('resolve_ref') +8. **dispose** - calls close_repo and clears path; no-ops when no repo; clears path even on failure +9. **error propagation** - invoke rejections propagate for loadRepo and getDiff +10. **singleton reuse** - same instance reuses repo path; allows loading new repo after dispose + +## Unresolved Risks + +None. + +VERIFIED: YES diff --git a/.codex/ralph-gitcontext/verify/theme-management-tests.md b/.codex/ralph-gitcontext/verify/theme-management-tests.md new file mode 100644 index 0000000..ea6d5fb --- /dev/null +++ b/.codex/ralph-gitcontext/verify/theme-management-tests.md @@ -0,0 +1,49 @@ +# Verification Report: theme-management-tests + +## Story: Frontend: theme management integration tests + +### Pass Criteria + +- [x] At least 5 tests covering: system default, toggle, persistence, fallback, DOM attribute +- [x] matchMedia mock supports both light and dark system preferences +- [x] All tests pass with `npm --workspace apps/desktop run test` + +### Test Results + +**Command:** `npm --workspace apps/desktop run test` +**Result:** PASS — 226 tests passed (10 theme tests), 0 failures + +**Theme tests (10 total):** +1. defaults to light when system prefers light +2. defaults to dark when system prefers dark +3. toggles between light and dark +4. persists theme choice to localStorage under gc.theme +5. restores persisted theme from localStorage on mount +6. falls back to system preference when localStorage has no theme +7. sets data-theme attribute on documentElement +8. falls back to system default when localStorage has corrupt theme value +9. responds to system preference changes when no explicit theme is set +10. removes matchMedia listener on unmount + +### Quality Gates + +| Gate | Result | +|------|--------| +| `npm --workspace apps/desktop run test` | PASS (226 tests) | +| `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml` | PASS (44 tests) | +| `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings` | PASS | +| `npm run web:build` | PASS | + +### Files Changed + +- `apps/desktop/src/hooks/useTheme.ts` — extracted hook from App.tsx inline theme logic +- `apps/desktop/src/App.tsx` — refactored to use useTheme hook +- `apps/desktop/src/__tests__/theme.test.ts` — 10 tests for theme management + +### Implementation Notes + +- Extracted inline theme management logic (useState, useEffect for matchMedia, localStorage, data-theme attribute) from App.tsx into a reusable `useTheme` hook +- matchMedia mock uses `createMatchMediaMock(prefersDark)` helper with `trigger(matches)` for dynamic preference switching +- Corrupt localStorage values (anything other than 'light' or 'dark') fall back to system preference and are cleaned up via `localStorage.removeItem` + +VERIFIED: YES diff --git a/.codex/ralph-gitcontext/verify/token-counts-context-tests.md b/.codex/ralph-gitcontext/verify/token-counts-context-tests.md new file mode 100644 index 0000000..f0bf642 --- /dev/null +++ b/.codex/ralph-gitcontext/verify/token-counts-context-tests.md @@ -0,0 +1,39 @@ +# Verification Report: token-counts-context-tests + +## Story +**ID:** token-counts-context-tests +**Title:** Frontend: TokenCountsContext provider tests + +## Pass Criteria +- [x] At least 5 tests covering: context availability, count updates, busy flag, progress, unmount safety +- [x] No 'state update on unmounted component' warnings +- [x] All tests pass with `npm --workspace apps/desktop run test` + +## Tests Created +9 tests in `apps/desktop/src/context/TokenCountsContext.test.tsx`: + +1. **makes token counts available to children via useContext** - Verifies counts, total, busy are accessible via useTokenCountsContext +2. **updates counts when selectedPaths change** - Verifies counts update on rerender with new selectedPaths +3. **busy flag reflects computation state** - Verifies busy=true during computation and busy=false when done +4. **progress updates correctly when onBatch is called** - Verifies completed/total/percent via onBatch callback +5. **total is the sum of all individual file counts** - Verifies total matches sum of individual counts +6. **handles unmount during async counting without state-update-after-unmount warnings** - Console.error spy detects no state update warnings after unmount +7. **progress percent handles edge cases correctly** - Verifies percent=100 when total<=0, clamped to 100 when completed>total, correct rounding +8. **throws when useTokenCountsContext is used outside provider** - Verifies meaningful error message +9. **passes all props to useTokenCounts correctly** - Verifies gitClient, baseRef, compareRef, selectedPaths, statusByPath, diffContextLines, includeBinaryPaths, onBatch are forwarded + +## Commands Run +| Command | Result | +|---------|--------| +| `npm --workspace apps/desktop run test` | PASS (216 tests, 16 files) | +| `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml` | PASS (44 tests) | +| `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings` | PASS | +| `npm run web:build` | PASS | + +## Files Changed +- `apps/desktop/src/context/TokenCountsContext.test.tsx` (created, 9 tests) + +## Unresolved Risks +None. + +VERIFIED: YES diff --git a/.codex/ralph-gitcontext/verify/use-file-tree-comprehensive.md b/.codex/ralph-gitcontext/verify/use-file-tree-comprehensive.md new file mode 100644 index 0000000..acc3998 --- /dev/null +++ b/.codex/ralph-gitcontext/verify/use-file-tree-comprehensive.md @@ -0,0 +1,50 @@ +# Verification Report: use-file-tree-comprehensive + +## Story: Frontend: useFileTree hook comprehensive tests + +### Pass Criteria + +- [x] At least 12 total tests (including existing 3) covering tree building, status, binary detection, filtering, selection, large repo, race conditions + - **Result:** 22 total tests (3 existing + 19 new) covering all specified areas +- [x] Tree structure verified: directories contain children, files are leaves + - **Result:** Test "builds correct hierarchical tree from flat file list" verifies dir nodes have children arrays, file nodes have no children (undefined) +- [x] Race condition test verifies stale results are discarded + - **Result:** Test "ignores stale diff results when a newer request supersedes" uses deferred promise for stale request; after fresh request completes, stale resolution does not overwrite tree state +- [x] All tests pass with `npm --workspace apps/desktop run test` + - **Result:** PASS - 94 tests pass (22 in useFileTree.test.ts) + +### Quality Gates + +| Command | Result | +|---------|--------| +| `npm --workspace apps/desktop run test` | PASS (94 tests) | +| `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml` | PASS (44 tests) | +| `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings` | PASS | +| `npm run web:build` | PASS | + +### Test Coverage Summary (22 tests) + +1. addSelectedPaths keeps existing and adds new unique paths (existing) +2. removeSelectedPathsByPredicate removes only matching test paths (existing) +3. removeSelectedPathsByPredicate is a no-op when nothing matches (existing) +4. builds correct hierarchical tree from flat file list +5. sets status markers (add/modify/remove/unchanged) correctly on file nodes +6. binary files detected by extension get isLikelyBinary flag +7. diffSequence increments on each computation +8. resets state when gitClient is null +9. auto-enables showChangedOnly when file count exceeds threshold +10. ignores stale diff results when a newer request supersedes +11. expands and collapses directory paths (toggleExpand) +12. selects and deselects files (toggleSelect) +13. selectAll respects showChangedOnly +14. selectAll includes unchanged files when showChangedOnly is false +15. selectAll respects filter text +16. deselectAll clears selected paths for visible files +17. expandAll sets all directory paths +18. collapseAll clears all expanded paths +19. expands parent directories of target file (revealPath) +20. showChangedOnly defaults to true and unchanged files are filterable +21. auto-selects add and modify files, not remove files +22. resets state when base and compare branches are the same + +VERIFIED: YES diff --git a/.codex/ralph-gitcontext/verify/use-git-repository-hook.md b/.codex/ralph-gitcontext/verify/use-git-repository-hook.md new file mode 100644 index 0000000..dbd6066 --- /dev/null +++ b/.codex/ralph-gitcontext/verify/use-git-repository-hook.md @@ -0,0 +1,46 @@ +# Verification Report: use-git-repository-hook + +## Story: Frontend: useGitRepository hook tests + +### Pass Criteria + +- [x] At least 8 tests covering: initial state, load success, load error, refresh, reset, branch persistence, watcher events, flip +- [x] State transitions verified (idle→loading→ready, idle→loading→error) +- [x] localStorage mock verifies branch persistence per repo path +- [x] All tests pass with `npm --workspace apps/desktop run test` + +### Test Summary + +12 tests created in `apps/desktop/src/hooks/useGitRepository.test.ts`: + +1. **Initial state** — idle with empty branches, null currentDir, diffTrigger=0 +2. **Load success** — idle→loading→ready transition, branches set, default branch selection (main/dev), AppStatus callbacks +3. **Load error** — idle→loading→error transition, error message propagated, AppStatus ERROR callback +4. **Refresh** — reloads branches from current directory, preserves selection via preferredBranches +5. **Reset** — clears all state back to idle, calls dispose(), AppStatus IDLE callback +6. **Branch persistence** — saves to localStorage on load, saves on manual change via useEffect, restores saved branches on reload +7. **Workdir-changed event** — increments diffTrigger when __WORKDIR__ is selected (after 300ms debounce) +8. **Workdir-changed ignored** — does NOT increment diffTrigger when __WORKDIR__ is not selected +9. **Flip branches** — setBaseBranch/setCompareBranch can swap base and compare +10. **Refresh returns false** — returns false when no directory is loaded +11. **Single-branch repo** — compare defaults to same as base when only one branch exists +12. **Wrong repo ignored** — workdir-changed event for different repoPath is ignored + +### Mocking Strategy + +- `TauriGitService` mocked via `vi.hoisted()` + `vi.mock()` with class definition pattern +- `@tauri-apps/api/event` `listen` mocked to capture callbacks, enabling manual event emission in tests +- `@tauri-apps/plugin-dialog` mocked (not exercised in these tests) +- `localStorage` stubbed via `vi.stubGlobal()` with `Map`-based storage (pattern from workspaceStore.test.ts) +- Fake timers used to test debounce behavior + +### Verification Commands + +``` +npm --workspace apps/desktop run test -> PASS (75 tests, 12 new) +cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml -> PASS (44 tests) +cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings -> PASS +npm run web:build -> PASS +``` + +VERIFIED: YES diff --git a/.codex/ralph-gitcontext/verify/use-token-counts-hook.md b/.codex/ralph-gitcontext/verify/use-token-counts-hook.md new file mode 100644 index 0000000..fc417ed --- /dev/null +++ b/.codex/ralph-gitcontext/verify/use-token-counts-hook.md @@ -0,0 +1,49 @@ +# Verification Report: use-token-counts-hook + +## Story +- **ID**: use-token-counts-hook +- **Title**: Frontend: useTokenCounts hook tests + +## Acceptance Criteria + +- [x] At least 7 tests covering: count update, binary handling (both modes), progress, cancellation, empty selection, context lines effect +- [x] Mock tiktoken returns deterministic token counts +- [x] Cancellation test verifies no state update after abort +- [x] All tests pass with `npm --workspace apps/desktop run test` + +## Tests Written (10 total) + +1. **updates token counts when selectedPaths change** - Verifies counts update on rerender with new paths +2. **counts binary files as header-only tokens when includeBinaryPaths is true** - Extension-detected binary (.png) gets header tokens, readFile not called +3. **returns 0 tokens for binary files when includeBinaryPaths is false** - Extension-detected binary (.jpg) gets 0 tokens +4. **reports progress via onBatch callback** - Verifies initial (0, total) and final (total, total) progress calls +5. **does not update state after cancellation (abort)** - Unmount aborts; late-resolving readFile doesn't update counts +6. **returns empty counts and zero total for empty selection** - Empty Set produces no counts +7. **produces more tokens with more context lines** - diffContextLines=0 < diffContextLines=999 +8. **total is sum of all individual file counts** - Verifies total === sum of individual counts +9. **handles runtime-detected binary files** - Non-binary extension but binary:true response handled both with and without includeBinaryPaths +10. **returns empty counts when gitClient is null** - Null client produces empty counts, no readFile calls + +## Mock Strategy + +- **tiktoken**: Mocked via `vi.mock('../utils/tokenizer')` returning deterministic word-count (split on whitespace) +- **gitClient**: Plain object with mocked `readFile` method (no TauriGitService module mock needed since it's a type-only import) + +## Commands Run + +| Command | Result | +|---------|--------| +| `npm --workspace apps/desktop run test` | PASS (104 tests, 9 files) | +| `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml` | PASS (44 tests) | +| `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings` | PASS (clean) | +| `npm run web:build` | PASS | + +## Files Changed + +- `apps/desktop/src/hooks/useTokenCounts.test.ts` (new) + +## Risks / Follow-ups + +None identified. + +VERIFIED: YES diff --git a/.codex/ralph-gitcontext/verify/workspace-lifecycle-tests.md b/.codex/ralph-gitcontext/verify/workspace-lifecycle-tests.md new file mode 100644 index 0000000..5adb8b0 --- /dev/null +++ b/.codex/ralph-gitcontext/verify/workspace-lifecycle-tests.md @@ -0,0 +1,51 @@ +# Verification: workspace-lifecycle-tests + +## Story +**ID:** workspace-lifecycle-tests +**Title:** Frontend: workspace save/restore lifecycle integration tests + +## Pass Criteria + +- [x] At least 7 tests covering: save, restore, auto-detect, refresh, delete, missing branch, session auto-persist +- [x] Branch and selection state correctly captured and restored +- [x] Race condition between workspace switches handled +- [x] All tests pass with `npm --workspace apps/desktop run test` + +## Test Results + +``` + ✓ src/__tests__/workspaceLifecycle.test.ts (10 tests) + ✓ saving a workspace captures current branch selection and file selection + ✓ loading a saved workspace restores branches and selection for diff + ✓ workspace auto-detection on repo open matches by path + ✓ workspace refresh preserves current selection + ✓ deleting a workspace removes it from store and clears selection + ✓ workspace with missing branch falls back gracefully + ✓ workspace session auto-persists on settings change + ✓ switching workspaces cancels pending selection restore from previous workspace + ✓ full lifecycle: create → save → close → reopen → modify → persist → reload + ✓ multiple workspaces maintain isolated sessions +``` + +## Quality Gates + +| Command | Result | +|---------|--------| +| `npm --workspace apps/desktop run test` | PASS (288 tests, 20 files) | +| `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml` | PASS (44 tests) | +| `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings` | PASS | +| `npm run web:build` | PASS | + +## Files Changed + +- `apps/desktop/src/__tests__/workspaceLifecycle.test.ts` (new - 10 integration tests) + +## Implementation Notes + +- Tests simulate the full workspace lifecycle using workspaceStore functions +- Helper functions mirror App.tsx patterns: buildSnapshot, commitWorkspaceStore +- localStorage mocked via vi.stubGlobal with Map-backed store +- Race condition tested by simulating the workspace switch request ID counter pattern +- Missing branch fallback tested by checking branch existence against available branches + +VERIFIED: YES diff --git a/.codex/ralph-gitcontext/verify/workspace-store-comprehensive.md b/.codex/ralph-gitcontext/verify/workspace-store-comprehensive.md new file mode 100644 index 0000000..17e0d47 --- /dev/null +++ b/.codex/ralph-gitcontext/verify/workspace-store-comprehensive.md @@ -0,0 +1,47 @@ +# Verification: workspace-store-comprehensive + +## Story: Frontend: workspaceStore comprehensive tests + +## Pass Criteria + +- [x] At least 12 total tests (including existing 5) covering all edge cases + - 16 total tests (4 existing + 12 new) +- [x] Selection limit enforcement verified (paths truncated at 5000) + - Test generates 5100 unique paths, verifies only 5000 stored, confirms first/last path ordering +- [x] Corrupt data handling verified (graceful degradation) + - Test checks 8 corrupt variants: invalid JSON, null, number, string, array, wrong workspaces type, null/bad elements, empty id, empty name — all return empty store +- [x] All tests pass with `npm --workspace apps/desktop run test` + - 141 total tests pass (16 workspaceStore tests) + +## Quality Gates + +| Gate | Result | +|------|--------| +| `npm --workspace apps/desktop run test` | PASS (141 tests) | +| `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml` | PASS (44 tests) | +| `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings` | PASS | +| `npm run web:build` | PASS | + +## Tests Added (12 new, 16 total) + +1. loads an empty store when localStorage content is invalid (existing) +2. creates and persists a workspace with a default session snapshot (existing) +3. updates a workspace session without losing previous settings defaults (existing) +4. partitions restored and missing selected files (existing) +5. upsertWorkspace with existing ID updates rather than creates duplicate +6. removeWorkspace with non-existent ID is a no-op +7. getWorkspaceSelectionRestore is case-sensitive +8. enforces MAX_PERSISTED_SELECTIONS (5000) limit on save +9. listWorkspaceItems returns sorted by lastOpenedAt descending +10. setActiveWorkspace/getWorkspaceById round-trips correctly +11. handles corrupt localStorage data gracefully (returns empty store) +12. workspace session settings merge with defaults (missing keys filled in) +13. findWorkspaceByPath matches exact paths only (not substrings) +14. upsertWorkspace by path match prevents duplicates +15. removeWorkspace clears activeWorkspaceId when removing the active workspace +16. persisted workspace survives save/load round-trip with all session data + +## Commit +eafb12a test(desktop): add comprehensive Vitest tests for workspaceStore + +VERIFIED: YES diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..673e6f1 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,42 @@ +# Dependencies (large, rebuilt in container) +node_modules/ +*/node_modules/ + +# Build outputs +apps/web/dist/ +apps/web/dist-ssr/ +apps/desktop/dist/ +apps/desktop/src-tauri/target/ +dist/ +dist-all/ +build/ +web/ + +# Editor / OS +.vscode/ +.idea/ +.DS_Store +*.suo +*.sw? + +# Logs +*.log +npm-debug.log* + +# Local env (secrets — passed via docker-compose environment) +.env +.env.* +!.env.example + +# Git history (not needed in build context) +.git/ + +# Test outputs +coverage/ +.nyc_output/ +apps/web/test-results/ + +# Misc +.husky/ +.eslintcache +*.tsbuildinfo diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..01a6d53 --- /dev/null +++ b/.env.example @@ -0,0 +1,20 @@ +# Copy this file to .env and fill in your values. +# .env is gitignored — never commit real secrets. + +# Claude Code auth — use one of the two options below: +# +# Option A — API key (standard) +ANTHROPIC_API_KEY=sk-ant-... +# +# Option B — OAuth token (Claude.ai subscription / browser login) +ANTHROPIC_AUTH_TOKEN= +# +# Optional: custom endpoint (enterprise gateway, proxy, etc.) +ANTHROPIC_BASE_URL= + +# Allow claude --dangerously-skip-permissions inside Docker (container runs as root) +CLAUDE_CODE_BUBBLEWRAP=1 + +# Optional: git identity used for agent checkpoint commits inside the container +GIT_USER_NAME=Claude Loop +GIT_USER_EMAIL=claude-loop@local diff --git a/.gitignore b/.gitignore index d77c65a..97c7c8a 100644 --- a/.gitignore +++ b/.gitignore @@ -162,12 +162,22 @@ apps/desktop/src-tauri/Cargo.lock *.njsproj *.sln *.sw? -# Ignore all Markdown files -*.md # But keep README.md !README.md agent/ .cursor/ -.claude/ \ No newline at end of file +.claude/ + +# Docker local overrides (machine-specific tweaks, not committed) +docker-compose.override.yml + +# Ralph loop — runtime state (logs, temp files) +# Keep: .ralph/progress.md, .ralph/guardrails.md (learning artifacts) +# Keep: .codex/ralph-gitcontext/verify/ (verification reports) +.ralph/runs/ +.ralph/.tmp/ +.ralph/errors.log +.ralph/activity.log +.codex/ralph-gitcontext/logs/ \ No newline at end of file diff --git a/.ralph/guardrails.md b/.ralph/guardrails.md new file mode 100644 index 0000000..e7b8dff --- /dev/null +++ b/.ralph/guardrails.md @@ -0,0 +1,20 @@ +# Guardrails (Signs) + +> Lessons learned from failures. Read before acting. + +## Core Signs + +### Sign: Read Before Writing +- **Trigger**: Before modifying any file +- **Instruction**: Read the file first +- **Added after**: Core principle + +### Sign: Test Before Commit +- **Trigger**: Before committing changes +- **Instruction**: Run required tests and verify outputs +- **Added after**: Core principle + +--- + +## Learned Signs + diff --git a/.ralph/progress.md b/.ralph/progress.md new file mode 100644 index 0000000..67b5d31 --- /dev/null +++ b/.ralph/progress.md @@ -0,0 +1,750 @@ +# Progress Log +Started: Sun Feb 22 10:38:17 UTC 2026 + +## Codebase Patterns +- (add reusable patterns here) + +--- + +## 2026-02-22 - rust-git-diff: Rust backend: git_diff command tests +Thread: claude session +Run: 20260222-104122-$ (iteration 1) +Run log: /workspace/.ralph/runs/run-20260222-104122-$-iter-1.log +Run summary: /workspace/.ralph/runs/run-20260222-104122-$-iter-1.md +- Guardrails reviewed: yes +- No-commit run: false +- Commit: a886d80 test(desktop): add comprehensive cargo tests for git_diff command +- Post-commit status: clean (only untracked: auto-generated Tauri linux-schema.json) +- Verification: + - Command: `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml` -> PASS (9/9 tests) + - Command: `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings` -> PASS + - Command: `cargo fmt --check --manifest-path apps/desktop/src-tauri/Cargo.toml` -> PASS + - Command: `npm run web:build` -> PASS +- Files changed: + - apps/desktop/src-tauri/Cargo.toml (added tempfile dev-dependency) + - apps/desktop/src-tauri/src/git.rs (8 new tests + test helper + clippy fixes + fmt) + - apps/desktop/src-tauri/src/lib.rs (cargo fmt whitespace only) + - .codex/ralph-gitcontext/verify/rust-git-diff.md (verification report) +- What was implemented: + - 8 comprehensive unit tests for the git_diff function covering all acceptance criteria + - create_test_repo() helper that initializes a temporary git repo with an initial commit on "main" + - Tests verify: identical branches, add, modify, remove, rename (with old_path), WORKDIR sentinel, invalid branch error, binary file detection +- **Learnings for future iterations:** + - Docker environment lacks Tauri system deps (glib-2.0, gtk, webkit2gtk); need `apt-get install` before cargo builds + - `@rollup/rollup-linux-arm64-gnu` needed for web build on arm64 Linux Docker + - Pre-existing code had clippy warnings; fixing them is necessary to pass the `-D warnings` quality gate + - `cargo fmt` auto-fixes both existing code and new code formatting +--- + +## 2026-02-22 - rust-git-read-file: Rust backend: read_file_blob command tests +Thread: claude session +Run: 20260222-104122-$ (iteration 2) +Run log: /workspace/.ralph/runs/run-20260222-104122-$-iter-2.log +Run summary: /workspace/.ralph/runs/run-20260222-104122-$-iter-2.md +- Guardrails reviewed: yes +- No-commit run: false +- Commit: 3cec463 test(desktop): add comprehensive cargo tests for read_file_blob command +- Post-commit status: pre-existing uncommitted files only (.agents/tasks/prd.json, linux-schema.json) +- Verification: + - Command: `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml` -> PASS (18/18 tests) + - Command: `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings` -> PASS + - Command: `cargo fmt --check --manifest-path apps/desktop/src-tauri/Cargo.toml` -> PASS + - Command: `npm run web:build` -> PASS +- Files changed: + - apps/desktop/src-tauri/src/git.rs (9 new read_file_blob tests) + - .codex/ralph-gitcontext/verify/rust-git-read-file.md (verification report) +- What was implemented: + - 9 comprehensive unit tests for the read_file_blob function covering all acceptance criteria + - Tests verify: text file content match, missing file (notFound=true), binary detection (null bytes), WORKDIR filesystem read, WORKDIR missing file, non-UTF8 lossy conversion (git blob), non-UTF8 lossy conversion (WORKDIR), bad ref error, WORKDIR binary detection + - Binary detection tested with actual null byte content in both git blob and workdir paths + - Lossy UTF-8 conversion verified: invalid bytes (0xC0, 0xC1, 0xFE, 0xFF) produce U+FFFD replacement characters while preserving valid surrounding text +- **Learnings for future iterations:** + - Reuse existing create_test_repo() helper from the git_diff story + - cargo fmt must be run after writing code; clippy and fmt are separate checks + - read_file_blob has two distinct code paths (git blob vs WORKDIR) that both need testing +--- + +## 2026-02-22 - rust-git-list-files: Rust backend: list_files and list_files_with_oids tests +Thread: claude session +Run: 20260222-104122-$ (iteration 3) +Run log: /workspace/.ralph/runs/run-20260222-104122-$-iter-3.log +Run summary: /workspace/.ralph/runs/run-20260222-104122-$-iter-3.md +- Guardrails reviewed: yes +- No-commit run: false +- Commit: 82897c4 test(desktop): add comprehensive cargo tests for list_files, list_files_with_oids, and resolve_ref +- Post-commit status: pre-existing uncommitted files only (.agents/tasks/prd.json, linux-schema.json) +- Verification: + - Command: `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml` -> PASS (28/28 tests) + - Command: `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings` -> PASS + - Command: `npm --workspace apps/desktop run test` -> PASS (45/45 tests) + - Command: `npm run web:build` -> PASS +- Files changed: + - apps/desktop/src-tauri/src/git.rs (11 new tests for list_files, list_files_with_oids, resolve_ref) + - .codex/ralph-gitcontext/verify/rust-git-list-files.md (verification report) +- What was implemented: + - 11 comprehensive unit tests covering all acceptance criteria: + - list_files (6 tests): committed files returned, nested dirs flatten to full paths, empty tree returns empty, WORKDIR returns filesystem files, WORKDIR respects .gitignore (*.log, build/) + - list_files_with_oids (2 tests): valid 40-char hex OIDs, multiple files with unique OIDs for different content + - resolve_ref (3 tests): valid branch resolves to 40-char hex OID, invalid ref returns error, HEAD resolves to same OID as main + - .gitignore test verifies both glob patterns (*.log) and directory patterns (build/) are respected + - OID validation checks both length (40 chars) and hex character set +- **Learnings for future iterations:** + - Empty tree can be created via repo.treebuilder(None).unwrap().write() without needing any files + - list_workdir_files uses the `ignore` crate WalkBuilder which automatically respects .gitignore + - Reuse existing create_test_repo() helper; only need custom setup for empty-tree edge case +--- + +## 2026-02-22 - rust-watcher: Rust backend: file watcher tests +Thread: claude session +Run: 20260222-104122-$ (iteration 4) +Run log: /workspace/.ralph/runs/run-20260222-104122-$-iter-4.log +Run summary: /workspace/.ralph/runs/run-20260222-104122-$-iter-4.md +- Guardrails reviewed: yes +- No-commit run: false +- Commit: 341f7b4 test(desktop): add comprehensive cargo tests for watcher path filtering +- Post-commit status: pre-existing uncommitted files only (.agents/tasks/prd.json, linux-schema.json) +- Verification: + - Command: `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml` -> PASS (44/44 tests) + - Command: `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings` -> PASS + - Command: `cargo fmt --check --manifest-path apps/desktop/src-tauri/Cargo.toml` -> PASS + - Command: `npm --workspace apps/desktop run test` -> PASS (45/45 tests) + - Command: `npm run web:build` -> PASS +- Files changed: + - apps/desktop/src-tauri/src/watcher.rs (extracted classify_path + EventKind, added 17 tests) + - apps/desktop/src-tauri/src/git.rs (cargo fmt formatting fix only) + - .codex/ralph-gitcontext/verify/rust-watcher.md (verification report) +- What was implemented: + - Extracted path-filtering logic from the watcher callback into a pure `classify_path(path, repo_root, git_dir) -> EventKind` function + - Added `EventKind` enum with `RefsChanged`, `WorkdirChanged(String)`, and `Ignored` variants + - Refactored the `RepoWatcher::new` callback to use `classify_path` via match + - 17 unit tests covering all acceptance criteria: + - Ignored paths (7): node_modules, nested node_modules, .git/objects, .git/logs, target/ at root, nested target/, .git/index + - Workdir-changed (3): regular source file, root-level file, deeply nested file + - Refs-changed (4): .git/refs/heads, .git/refs/tags, .git/refs/remotes, .git/HEAD + - Edge cases (2): path outside repo, file named "target" in source dir not ignored +- **Learnings for future iterations:** + - When watcher/callback logic is tightly coupled to Tauri AppHandle, extract the pure logic into a standalone function for testability + - The refactoring pattern (extract pure function + test it) preserves exact behavior while enabling comprehensive testing + - Pre-existing cargo fmt issues in other files should be fixed as part of the quality gates +--- + +## 2026-02-22 - tauri-git-service: Frontend: TauriGitService unit tests +Thread: claude session +Run: 20260222-104122-$ (iteration 5) +Run log: /workspace/.ralph/runs/run-20260222-104122-$-iter-5.log +Run summary: /workspace/.ralph/runs/run-20260222-104122-$-iter-5.md +- Guardrails reviewed: yes +- No-commit run: false +- Commit: 05d2ece test(desktop): add comprehensive Vitest tests for TauriGitService +- Post-commit status: pre-existing uncommitted files only (.agents/tasks/prd.json, linux-schema.json) +- Verification: + - Command: `npm --workspace apps/desktop run test` -> PASS (63 tests, 18 new) + - Command: `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml` -> PASS (44 tests) + - Command: `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings` -> PASS + - Command: `npm run web:build` -> PASS +- Files changed: + - apps/desktop/src/services/TauriGitService.test.ts (new, 18 tests) + - .codex/ralph-gitcontext/verify/tauri-git-service.md (verification report) +- What was implemented: + - 18 comprehensive Vitest unit tests for the TauriGitService class + - Mocked @tauri-apps/api/core invoke function using vi.mock + - Tests cover all 7 GitService interface methods: loadRepo, listBranches, getDiff, readFile, listFiles, listFilesWithOids, resolveRef + - Dispose tests: close_repo invocation, no-op when no repo, path cleared even on failure + - Error propagation: invoke rejections propagate for loadRepo and getDiff + - Singleton/reuse: same instance reuses path, allows new repo after dispose + - readFile field mapping: not_found -> notFound, binary files, missing files +- **Learnings for future iterations:** + - vi.mock must be called at module level before imports for Vitest hoisting to work correctly + - vi.mocked(invoke) provides proper typing for mock assertions + - Each test needs to loadRepo first since the service requires repoPath to be set + - mockClear() between loadRepo and the method under test keeps assertions clean +--- + +## 2026-02-22 - use-git-repository-hook: Frontend: useGitRepository hook tests +Thread: claude session +Run: 20260222-104122-$ (iteration 6) +Run log: /workspace/.ralph/runs/run-20260222-104122-$-iter-6.log +Run summary: /workspace/.ralph/runs/run-20260222-104122-$-iter-6.md +- Guardrails reviewed: yes +- No-commit run: false +- Commit: 22082eb test(desktop): add comprehensive Vitest tests for useGitRepository hook +- Post-commit status: pre-existing uncommitted files only (.agents/tasks/prd.json, linux-schema.json) +- Verification: + - Command: `npm --workspace apps/desktop run test` -> PASS (75 tests, 12 new) + - Command: `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml` -> PASS (44 tests) + - Command: `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings` -> PASS + - Command: `npm run web:build` -> PASS +- Files changed: + - apps/desktop/src/hooks/useGitRepository.test.ts (new, 12 tests) + - .codex/ralph-gitcontext/verify/use-git-repository-hook.md (verification report) +- What was implemented: + - 12 comprehensive renderHook tests for the useGitRepository hook covering all acceptance criteria + - Tests cover: initial idle state, load success (idle→loading→ready), load error (idle→loading→error), refresh with preserved selection, reset to idle, localStorage branch persistence (save/restore), workdir-changed event increments diffTrigger (with 300ms debounce), workdir-changed ignored when WORKDIR not selected, branch flipping via setBaseBranch/setCompareBranch, refreshRepo returns false with no dir, single-branch repo defaults, wrong repo path events ignored + - Mocked TauriGitService with vi.hoisted() + vi.mock() class pattern, @tauri-apps/api/event listen with captured callbacks, localStorage via vi.stubGlobal with Map-based store +- **Learnings for future iterations:** + - vi.mock factory is hoisted before variable declarations; must use vi.hoisted() to define mock references used inside factory + - vi.fn().mockImplementation() as a constructor doesn't work reliably with `new`; use a class definition in the mock factory instead + - The eventListeners Map for @tauri-apps/api/event must be created in vi.hoisted() scope to be accessible in the mock factory + - vi.useFakeTimers() is needed to test debounce behavior; vi.advanceTimersByTime(350) past the 300ms debounce threshold + - The hook's branch selection logic skips __WORKDIR__ when choosing compare, but falls back to it when it's the only other branch +--- + +## 2026-02-22 - use-file-tree-comprehensive: Frontend: useFileTree hook comprehensive tests +Thread: claude session +Run: 20260222-104122-$ (iteration 7) +Run log: /workspace/.ralph/runs/run-20260222-104122-$-iter-7.log +Run summary: /workspace/.ralph/runs/run-20260222-104122-$-iter-7.md +- Guardrails reviewed: yes +- No-commit run: false +- Commit: ea87cba test(desktop): add comprehensive Vitest tests for useFileTree hook +- Post-commit status: clean +- Verification: + - Command: `npm --workspace apps/desktop run test` -> PASS (94 tests, 22 useFileTree tests) + - Command: `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml` -> PASS (44 tests) + - Command: `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings` -> PASS + - Command: `npm run web:build` -> PASS +- Files changed: + - apps/desktop/src/hooks/useFileTree.test.ts (expanded from 3 to 22 tests) +- What was implemented: + - 19 new tests (22 total) for the useFileTree hook covering all acceptance criteria: + - Tree building: hierarchical tree from flat paths, dirs sorted before files, files are leaves + - Status markers: add/modify/remove/unchanged set correctly on nodes and statusByPath map + - Binary detection: .png, .woff2 get isLikelyBinary=true, .ts gets false + - showChangedOnly filter: defaults to true, selectAll skips unchanged when true, includes all when false + - toggleExpand: expand/collapse directory paths + - toggleSelect: select/deselect files + - selectAll: respects showChangedOnly, respects filter text + - deselectAll: clears selected paths for visible files + - Large repo mode: >50000 files auto-enables showChangedOnly + - Race condition: stale diff results discarded via requestId mechanism + - expandAll/collapseAll: set/clear all directory paths + - diffSequence: increments on each computation + - revealPath: expands parent directories of target file + - Auto-selection: add/modify files selected, remove files not selected + - Same branch handling: resets state and shows message + - Null client: resets all state +- **Learnings for future iterations:** + - Large repo test with 50001 files is slow (~6s); set explicit 30s timeout + - Race condition testing requires careful mock design: deferred promise for the stale request, immediate resolution for the fresh one + - The hook's computeDiffAndTree checks requestId at each await point, so stale requests bail out early at getDiff level +--- + +## 2026-02-22 - use-token-counts-hook: Frontend: useTokenCounts hook tests +Thread: claude session +Run: 20260222-104122-$ (iteration 8) +Run log: /workspace/.ralph/runs/run-20260222-104122-$-iter-8.log +Run summary: /workspace/.ralph/runs/run-20260222-104122-$-iter-8.md +- Guardrails reviewed: yes +- No-commit run: false +- Commit: aa38d4f test(desktop): add comprehensive Vitest tests for useTokenCounts hook +- Post-commit status: clean (only pre-existing .agents/tasks/prd.json modified) +- Verification: + - Command: `npm --workspace apps/desktop run test` -> PASS (104 tests, 10 new) + - Command: `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml` -> PASS (44 tests) + - Command: `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings` -> PASS + - Command: `npm run web:build` -> PASS +- Files changed: + - apps/desktop/src/hooks/useTokenCounts.test.ts (new, 10 tests) + - .codex/ralph-gitcontext/verify/use-token-counts-hook.md (verification report) +- What was implemented: + - 10 comprehensive renderHook tests for the useTokenCounts hook covering all acceptance criteria + - Tests cover: count updates on selectedPaths change, binary files with includeBinaryPaths=true (header-only tokens), binary files with includeBinaryPaths=false (0 tokens), progress callback with correct completed/total values, cancellation via unmount/AbortSignal prevents stale state updates, empty selection returns zero counts, diffContextLines effect (more context = more tokens), total is sum of individual counts, runtime-detected binary files (non-binary extension but binary:true response), null gitClient returns empty counts + - Mock tiktoken returns deterministic word-count (split on whitespace) for predictable assertions + - gitClient mocked as plain object with readFile vi.fn() (TauriGitService is type-only import) +- **Learnings for future iterations:** + - TauriGitService is imported as type-only in useTokenCounts, so no module mock needed; a plain object with readFile suffices + - flushPromises helper with multiple setTimeout rounds ensures all nested async operations complete + - The hook uses mapWithConcurrency which processes in batches; progress callbacks fire at batch boundaries + - Cancellation test uses unmount() which triggers the effect cleanup (abortController.abort()) +--- + +## 2026-02-22 - diff-util-tests: Frontend: diff utility tests +Thread: claude session +Run: 20260222-104122-$ (iteration 9) +Run log: /workspace/.ralph/runs/run-20260222-104122-$-iter-9.log +Run summary: /workspace/.ralph/runs/run-20260222-104122-$-iter-9.md +- Guardrails reviewed: yes +- No-commit run: false +- Commit: 177218b test(desktop): add comprehensive Vitest tests for diff utilities +- Post-commit status: clean +- Verification: + - Command: `npm --workspace apps/desktop run test` -> PASS (129 tests, 25 new) + - Command: `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml` -> PASS (44 tests) + - Command: `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings` -> PASS + - Command: `npm run web:build` -> PASS +- Files changed: + - apps/desktop/src/utils/diff.test.ts (new, 25 tests) +- What was implemented: + - 25 comprehensive Vitest tests for createUnifiedDiffForPath (13 tests) and buildUnifiedDiffForStatus (12 tests) + - createUnifiedDiffForPath tests: modified file diff, added file (all additions), removed file (all deletions), unchanged (identical content → no hunks), CRLF normalization, stable CRLF-both-sides diff, context=0 (only changed lines), context=3 (3 lines around change), context=999 (full file), empty files, no trailing newline, output always ends with newline + - buildUnifiedDiffForStatus tests: modify, add, remove, unchanged text, unchanged binary (null), unchanged empty (null), binary modify (base/compare), binary add, binary remove, context option passthrough, notFound base side, CRLF through status wrapper + - Context lines verification: context=0 has no context lines in hunk, context=3 shows exactly 3 lines, context=999 shows all file lines + - CRLF normalization verified: \r\n → \n produces stable diffs, mixed line endings handled correctly +- **Learnings for future iterations:** + - The `diff` library's createTwoFilesPatch produces no hunks (no @@ markers) when both inputs are identical after normalization + - ensureFinalNewline ensures output always ends with \n even when the diff library output doesn't + - buildUnifiedDiffForStatus returns `oldText || null` for unchanged status, so empty string returns null + - Binary detection is per-side: modify returns null if either side is binary +--- + +## 2026-02-22 - workspace-store-comprehensive: Frontend: workspaceStore comprehensive tests +Thread: claude session +Run: 20260222-104122-$ (iteration 10) +Run log: /workspace/.ralph/runs/run-20260222-104122-$-iter-10.log +Run summary: /workspace/.ralph/runs/run-20260222-104122-$-iter-10.md +- Guardrails reviewed: yes +- No-commit run: false +- Commit: eafb12a test(desktop): add comprehensive Vitest tests for workspaceStore +- Post-commit status: clean (only pre-existing .agents/tasks/prd.json modified) +- Verification: + - Command: `npm --workspace apps/desktop run test` -> PASS (141 tests, 16 workspaceStore tests) + - Command: `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml` -> PASS (44 tests) + - Command: `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings` -> PASS + - Command: `npm run web:build` -> PASS +- Files changed: + - apps/desktop/src/utils/workspaceStore.test.ts (expanded from 4 to 16 tests) +- What was implemented: + - 12 new tests (16 total) for the workspaceStore covering all acceptance criteria: + - upsertWorkspace with existing ID updates rather than creates duplicate + - upsertWorkspace by path match prevents duplicates (no explicit ID needed) + - removeWorkspace with non-existent ID is a no-op (count/activeId unchanged) + - removeWorkspace clears activeWorkspaceId when removing the active workspace + - getWorkspaceSelectionRestore is case-sensitive (src/App.tsx != src/app.tsx) + - MAX_PERSISTED_SELECTIONS (5000) limit enforced (5100 paths truncated to 5000) + - listWorkspaceItems returns sorted by lastOpenedAt descending + - setActiveWorkspace/getWorkspaceById round-trips (set, clear with '', restore) + - corrupt localStorage data handled gracefully (8 variants all return empty store) + - workspace session settings merge with defaults (missing keys filled in) + - findWorkspaceByPath matches exact paths only (no substring, trailing slash normalized) + - save/load round-trip preserves all session data (branches, paths, settings, activeTab) +- **Learnings for future iterations:** + - findWorkspaceByPath uses toPathLookupKey() which lowercases paths, so lookups are case-insensitive (Windows-compatible) + - getWorkspaceSelectionRestore uses normalizeSelection which does NOT lowercase, making it case-sensitive by design + - MAX_PERSISTED_SELECTIONS is not exported, but can be tested by passing >5000 paths and verifying truncation + - corrupt localStorage variants include invalid JSON, wrong types, empty fields — all handled gracefully by sanitize functions +--- + +## 2026-02-22 - concurrency-util-tests: Frontend: concurrency utility tests +Thread: claude session +Run: 20260222-104122-$ (iteration 11) +Run log: /workspace/.ralph/runs/run-20260222-104122-$-iter-11.log +Run summary: /workspace/.ralph/runs/run-20260222-104122-$-iter-11.md +- Guardrails reviewed: yes +- No-commit run: false +- Commit: 31b8616 test(desktop): add comprehensive Vitest tests for concurrency utilities +- Post-commit status: clean +- Verification: + - Command: `npm --workspace apps/desktop run test` -> PASS (151 tests, 10 new) + - Command: `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml` -> PASS (44 tests) + - Command: `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings` -> PASS + - Command: `npm run web:build` -> PASS +- Files changed: + - apps/desktop/src/utils/concurrency.test.ts (new, 10 tests) + - .codex/ralph-gitcontext/verify/concurrency-util-tests.md (verification report) +- What was implemented: + - 10 comprehensive Vitest tests for concurrency utilities: + - mapWithConcurrency (7 tests): concurrency limit enforcement via in-flight tracking, result ordering preservation with varying delays, error propagation from rejected promises, empty array returns empty array, sequential processing with limit=1, limit greater than array length processes all concurrently, AbortSignal cancellation stops processing + - createConcurrencyLimiter (3 tests): concurrent execution limiting via in-flight tracking, result ordering from wrapped functions, error propagation from limited functions + - In-flight tracking pattern: increment counter before async work, record max, decrement after, assert max <= limit + - Result ordering verified with items that have different processing times to ensure completion order differs from input order +- **Learnings for future iterations:** + - mapWithConcurrency uses batch-based approach (slices of limit size processed with Promise.all), so in-flight count exactly equals batch size + - createConcurrencyLimiter uses queue-based approach (p-limit style), allowing more granular concurrency control + - AbortSignal is checked between batches, not during individual item processing + - Both utilities preserve result ordering: mapWithConcurrency via index tracking, createConcurrencyLimiter via Promise.all ordering +--- + +## 2026-02-22 - debounce-util-tests: Frontend: debounce utility tests +Thread: claude session +Run: 20260222-104122-$ (iteration 12) +Run log: /workspace/.ralph/runs/run-20260222-104122-$-iter-12.log +Run summary: /workspace/.ralph/runs/run-20260222-104122-$-iter-12.md +- Guardrails reviewed: yes +- No-commit run: false +- Commit: 71b5c5f test(desktop): add comprehensive Vitest tests for debounce utility +- Post-commit status: clean +- Verification: + - Command: `npm --workspace apps/desktop run test` -> PASS (157 tests, 6 new) + - Command: `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml` -> PASS (44 tests) + - Command: `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings` -> PASS + - Command: `npm run web:build` -> PASS +- Files changed: + - apps/desktop/src/utils/debounce.test.ts (new, 6 tests) +- What was implemented: + - 6 comprehensive Vitest tests for the debounce utility function: + - Single invocation after burst: 5 rapid calls within delay only invoke once after timer expires + - Correct arguments: final call's arguments ('third') are used, not earlier calls + - Timer reset: rapid calls at 80ms intervals reset the 100ms timer, delaying execution + - Delay expiration: function fires exactly at the delay boundary (299ms no fire, 300ms fires) + - Returned function callable: two independent debounce cycles both invoke correctly + - Multiple arguments: complex argument types (number, string, object) passed correctly + - All tests use vi.useFakeTimers() for deterministic timing control + - vi.useRealTimers() in afterEach for cleanup +- **Learnings for future iterations:** + - vi.restoreAllTimers() is not a valid Vitest API; use vi.useRealTimers() instead for fake timer cleanup + - The desktop debounce does not have a cancel() method (unlike the web version); tests only cover the core debounce behavior +--- + +## [2026-02-22 12:55] - clipboard-batch-select-comprehensive: Frontend: clipboardBatchSelect comprehensive tests +Thread: +Run: 20260222-104122-$ (iteration 13) +Run log: /workspace/.ralph/runs/run-20260222-104122-$-iter-13.log +Run summary: /workspace/.ralph/runs/run-20260222-104122-$-iter-13.md +- Guardrails reviewed: yes +- No-commit run: false +- Commit: 8bc3877 test(desktop): add comprehensive Vitest tests for clipboardBatchSelect +- Post-commit status: clean +- Verification: + - Command: npm --workspace apps/desktop run test -> PASS (171 tests, 19 clipboardBatchSelect) + - Command: cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml -> PASS (44 tests) + - Command: cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings -> PASS + - Command: npm run web:build -> PASS +- Files changed: + - apps/desktop/src/utils/clipboardBatchSelect.test.ts (14 new tests, 19 total) + - apps/desktop/src/utils/clipboardBatchSelect.ts (bug fix: trailing slash stripping) + - .codex/ralph-gitcontext/verify/clipboard-batch-select-comprehensive.md (verification report) +- Added 14 new tests covering: Windows drive letter paths (C:\), case-insensitive matching, UNC paths (\\server\share), trailing slash stripping, whitespace-only line filtering, path deduplication, spaces and unicode in paths, relative ./paths, outsideRepoCount tracking, repo root rejection, end-to-end Windows path resolution +- Fixed bug: normalizeClipboardPath did not strip trailing slashes from input paths (only repo root was stripped), causing inconsistent results for directory-like paths +- **Learnings for future iterations:** + - clipboardBatchSelect already handles Windows drive letters via DRIVE_LETTER_ABS regex and case-insensitive comparison in equalForFs/startsWithForFs + - UNC paths (\\server\share) are normalized to //server/share which matches the startsWith('/') check in isAbsolutePath + - The resolveSelectablePaths function uses a Set for deduplication natively via matched.add() + - stripTrailingSlashes was already applied to repoRoot but not to the input line - this was the bug +--- + +## [2026-02-22T13:10:00Z] - selected-files-panel-tests: Frontend: SelectedFilesPanel component tests +Thread: +Run: 20260222-104122-$ (iteration 14) +Run log: /workspace/.ralph/runs/run-20260222-104122-$-iter-14.log +Run summary: /workspace/.ralph/runs/run-20260222-104122-$-iter-14.md +- Guardrails reviewed: yes +- No-commit run: false +- Commit: 6f6a5e3 test(desktop): add comprehensive Vitest tests for SelectedFilesPanel +- Post-commit status: clean +- Verification: + - Command: npm --workspace apps/desktop run test -> PASS (184 tests, 13 files) + - Command: cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml -> PASS (44 tests) + - Command: cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings -> PASS + - Command: npm run web:build -> PASS +- Files changed: + - apps/desktop/src/components/SelectedFilesPanel.test.tsx (new) +- 13 tests created covering: rendering with status icons, token sorting (high→low, low→high), name sorting (A→Z, Z→A), binary file indicators with disabled preview, remove/reveal/preview callbacks, empty state, refreshing/busy recalculating indicator, reveal button conditional rendering, locale-formatted token counts +- TokenCountsContext properly mocked via vi.mock +- **Learnings for future iterations:** + - Mocking context hooks via vi.mock of the module path is cleaner than wrapping with a provider for component tests + - The SelectedFilesPanel uses isBinaryPath from @gitcontext/core which checks file extension against a set of known binary extensions + - Default sort is tokens-desc; sort state is internal useState +--- + +## [2026-02-22T13:20:00Z] - context-footer-tests: Frontend: ContextFooter component tests +Thread: +Run: 20260222-104122-$ (iteration 15) +Run log: /workspace/.ralph/runs/run-20260222-104122-$-iter-15.log +Run summary: /workspace/.ralph/runs/run-20260222-104122-$-iter-15.md +- Guardrails reviewed: yes +- No-commit run: false +- Commit: d737065 test(desktop): add comprehensive Vitest tests for ContextFooter +- Post-commit status: clean +- Verification: + - Command: npm --workspace apps/desktop run test -> PASS (199 tests, 15 new in ContextFooter.test.tsx) + - Command: cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml -> PASS (44 tests) + - Command: cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings -> PASS + - Command: npm run web:build -> PASS +- Files changed: + - apps/desktop/src/components/ContextFooter.test.tsx (new, 15 tests) + - .codex/ralph-gitcontext/verify/context-footer-tests.md (verification report) +- What was implemented: + - 15 comprehensive Vitest tests for the ContextFooter component covering all acceptance criteria + - Token breakdown display: files count, file content (from context), instructions, file tree, and total tokens + - Progress bar: width matches percentage of limit, capped at 100%, hidden when limit=0 + - Over-limit: `.over-limit` class applied when totalTokens > limit, not when equal + - Copy button: disabled when filesCount=0 or disabled prop, enabled otherwise, clicking triggers onCopy + - Flash messages: success ("Copied!") and failure ("Copy failed") replace button text and disable button + - Token formatting: large numbers formatted with commas via toLocaleString() + - Busy state: "calculating..." text shown when context busy=true + - TokenCountsContext properly mocked via vi.mock of the module path +- **Learnings for future iterations:** + - ContextFooter derives totalTokens by adding filesTokens (from context) + instructionsTokens + fileTreeTokens (from props) + - The component uses toLocaleString() for formatting, which renders commas in en-US locale + - Progress bar is conditionally rendered only when limit > 0 + - Copy button is disabled during flash (!!copyFlash is truthy), not just when no files +--- + +## [2026-02-22 13:30:00] - right-panel-tabs-tests: Frontend: RightPanelTabs component tests +Thread: +Run: 20260222-104122-$ (iteration 16) +Run log: /workspace/.ralph/runs/run-20260222-104122-$-iter-16.log +Run summary: /workspace/.ralph/runs/run-20260222-104122-$-iter-16.md +- Guardrails reviewed: yes +- No-commit run: false +- Commit: 100106a test(desktop): add comprehensive Vitest tests for RightPanelTabs +- Post-commit status: clean +- Verification: + - Command: npm --workspace apps/desktop run test -> PASS (207 tests, 15 files, all passed) + - Command: cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml -> PASS (44 passed) + - Command: cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings -> PASS + - Command: npm run web:build -> PASS +- Files changed: + - apps/desktop/src/components/RightPanelTabs.test.tsx (created) + - .codex/ralph-gitcontext/verify/right-panel-tabs-tests.md (created) +- Created 8 tests for RightPanelTabs component covering tab rendering, badge count, tab switching with correct IDs ('files'/'settings'), active styling, children rendering, and content preservation on tab switch +- **Learnings for future iterations:** + - RightPanelTabs is a simple presentational component with no internal state - activeTab is fully controlled by parent + - Badge only renders when filesCount > 0 (conditional rendering with &&) + - The component renders children unconditionally in a .tab-content div regardless of active tab, preserving content across switches + - Active styling is applied via CSS class name 'active' on the tab-nav-item button +--- + +## [2026-02-22 13:42:00] - token-counts-context-tests: Frontend: TokenCountsContext provider tests +Thread: +Run: 20260222-104122-$ (iteration 17) +Run log: /workspace/.ralph/runs/run-20260222-104122-$-iter-17.log +Run summary: /workspace/.ralph/runs/run-20260222-104122-$-iter-17.md +- Guardrails reviewed: yes +- No-commit run: false +- Commit: 165b5a5 test(desktop): add comprehensive Vitest tests for TokenCountsContext +- Post-commit status: clean +- Verification: + - Command: npm --workspace apps/desktop run test -> PASS (216 tests, 16 files, all passed) + - Command: cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml -> PASS (44 passed) + - Command: cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings -> PASS + - Command: npm run web:build -> PASS +- Files changed: + - apps/desktop/src/context/TokenCountsContext.test.tsx (created) + - .codex/ralph-gitcontext/verify/token-counts-context-tests.md (created) +- What was implemented: + - 9 comprehensive Vitest tests for TokenCountsContext provider covering all acceptance criteria + - Tests cover: context availability via useContext, count updates on selectedPaths change, busy flag reflecting computation state, progress reports (completed/total/percent) via onBatch callback, total as sum of individual counts, unmount safety (no state-update-after-unmount warnings), progress percent edge cases (zero total, overflow), useTokenCountsContext throws outside provider, props forwarding to useTokenCounts + - Mock strategy: vi.mock of useTokenCounts hook, capturing onBatch callback for manual invocation in tests + - Consumer component pattern: renders context values as data-testid spans for assertion +- **Learnings for future iterations:** + - Mocking useTokenCounts and capturing onBatch via the mock factory allows testing the provider's progress state management independently + - React 18+ silently ignores state updates on unmounted components (no warning), but the test still verifies no console.error warnings occur + - The provider's percent calculation clamps to [0, 100] and uses Math.round, so edge cases like 0/0 -> 100%, 10/5 -> 100% should be tested + - Testing context providers is best done with a Consumer component pattern that exposes context values via data-testid attributes +--- + +## [2026-02-22 13:55] - theme-management-tests: Frontend: theme management integration tests +Thread: +Run: 20260222-104122-$ (iteration 18) +Run log: /workspace/.ralph/runs/run-20260222-104122-$-iter-18.log +Run summary: /workspace/.ralph/runs/run-20260222-104122-$-iter-18.md +- Guardrails reviewed: yes +- No-commit run: false +- Commit: d08c85d test(desktop): add comprehensive Vitest tests for useTheme hook +- Post-commit status: clean +- Verification: + - Command: `npm --workspace apps/desktop run test` -> PASS (226 tests, 10 theme tests) + - Command: `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml` -> PASS (44 tests) + - Command: `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings` -> PASS + - Command: `npm run web:build` -> PASS +- Files changed: + - apps/desktop/src/hooks/useTheme.ts (new, extracted hook from App.tsx) + - apps/desktop/src/App.tsx (refactored to use useTheme hook) + - apps/desktop/src/__tests__/theme.test.ts (new, 10 tests) +- What was implemented: + - Extracted theme management logic from App.tsx into a reusable `useTheme` hook + - 10 comprehensive Vitest renderHook tests covering all acceptance criteria: + - System default light: defaults to light when system prefers light + - System default dark: defaults to dark when system prefers dark + - Toggle: toggles between light and dark correctly + - Persistence: persists theme choice to localStorage under 'gc.theme' key + - Restore: restores persisted theme from localStorage on mount + - Fallback: falls back to system preference when no localStorage theme + - DOM attribute: sets data-theme attribute on document.documentElement + - Corrupt value: falls back to system default when localStorage has corrupt theme value + - System change: responds to system preference changes via matchMedia listener + - Cleanup: removes matchMedia listener on unmount + - matchMedia mock supports both light and dark system preferences and dynamic switching +- **Learnings for future iterations:** + - Extracting inline React state/effects into a custom hook is the cleanest way to make them testable with renderHook + - matchMedia mock needs both `matches` property and `addEventListener`/`removeEventListener` methods + - The `trigger` helper on matchMedia mock enables testing dynamic system preference changes + - Corrupt localStorage values (not 'light' or 'dark') are treated as null, which falls through to system preference and localStorage.removeItem cleans up +--- + +## 2026-02-22 14:12 UTC - app-copy-output-tests: Frontend: copy output generation tests +Thread: +Run: 20260222-104122-$ (iteration 19) +Run log: /workspace/.ralph/runs/run-20260222-104122-$-iter-19.log +Run summary: /workspace/.ralph/runs/run-20260222-104122-$-iter-19.md +- Guardrails reviewed: yes +- No-commit run: false +- Commit: aaa397f test(desktop): add comprehensive Vitest tests for copy output generation +- Post-commit status: clean (only .agents/tasks/prd.json modified, which is expected) +- Verification: + - Command: npm --workspace apps/desktop run test -> PASS (252 tests, 18 test files) + - Command: cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml -> PASS (44 tests) + - Command: cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings -> PASS + - Command: npm run web:build -> PASS +- Files changed: + - apps/desktop/src/utils/copyOutput.ts (new - extracted testable output-building functions) + - apps/desktop/src/__tests__/copyOutput.test.ts (new - 26 tests for copy output generation) + - apps/desktop/src/App.tsx (refactored copyAllSelected to use extracted functions) +- What was implemented: + - Extracted copy output logic from App.tsx into apps/desktop/src/utils/copyOutput.ts with pure functions: generateFileTreeText, buildHeader, buildFileSection, buildCopyOutput + - Refactored App.tsx to import and use the extracted functions, keeping behavior identical + - Created 26 comprehensive tests across 6 describe blocks: + - buildHeader (6 tests): repo path, branches, file count, instructions section, file tree toggle, unknown repo + - generateFileTreeText (3 tests): selected files filtering, directory exclusion, code fence wrapping + - buildFileSection (9 tests): modify diffs, add with unlimited context, add with limited context, remove diffs, binary by extension, binary by content flag, unchanged files, notFound handling, context lines effect + - buildCopyOutput (4 tests): multi-file concatenation order, file tree inclusion/exclusion, instructions section + - binary file handling (3 tests): extension-based, content-flag-based, non-binary .dat extension + - end-to-end format (1 test): verifies full output structure ordering and content +- **Learnings for future iterations:** + - The copyAllSelected function in App.tsx had inline logic for file tree generation, header building, and file section creation that was tightly coupled to the component. Extracting to pure functions is straightforward and makes testing easy. + - Binary detection is two-layered: extension-based (isBinaryPath from @gitcontext/core) and content-flag-based (binary property from ReadFileSide). Both paths need testing. + - The `add` status with unlimited context (Number.MAX_SAFE_INTEGER) produces a plain code block instead of a diff block - this is a special case worth testing. + - File content in code blocks retains its trailing newline, so expect `\n\`\`\`` patterns rather than exact end-of-content matches. +--- + +## [2026-02-22 14:19] - models-util-tests: Frontend: models utility tests +Thread: +Run: 20260222-104122-$ (iteration 20) +Run log: /workspace/.ralph/runs/run-20260222-104122-$-iter-20.log +Run summary: /workspace/.ralph/runs/run-20260222-104122-$-iter-20.md +- Guardrails reviewed: yes +- No-commit run: false +- Commit: f4976ab test(desktop): add comprehensive Vitest tests for models utility +- Post-commit status: clean (only .agents/tasks/prd.json modified — not our file) +- Verification: + - Command: npm --workspace apps/desktop run test -> PASS (265 tests, 19 files) + - Command: cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml -> PASS (44 tests) + - Command: cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings -> PASS + - Command: npm run web:build -> PASS +- Files changed: + - apps/desktop/src/utils/models.test.ts (new, 257 lines) +- What was implemented: + - Created 13 tests in two describe blocks for the models utility + - getModels tests (8): API fetch returns array, required fields present, unique IDs, positive context_length, non-empty list, cache hit for fresh data, refetch on stale cache, null on API failure + - Model selection persistence tests (5): save to gc.selectedModel, restore from localStorage, invalid model ID graceful fallback, empty localStorage handling, ModelInfo type compliance +- **Learnings for future iterations:** + - getModels() uses fetch to OpenRouter API — must mock global fetch for testing + - Model selection persistence is in App.tsx (not in models.ts), so persistence tests verify the localStorage pattern directly + - localStorage caching uses two keys: gc.models.cache and gc.models.fetchedAt with a 24-hour TTL + - The cache code falls through to refetch when JSON.parse fails on corrupt data +--- + +## [2026-02-22 14:30] - error-handling-tests: Frontend: global error handling and ErrorBanner integration tests +Thread: iteration-21 +Run: 20260222-104122-$ (iteration 21) +Run log: /workspace/.ralph/runs/run-20260222-104122-$-iter-21.log +Run summary: /workspace/.ralph/runs/run-20260222-104122-$-iter-21.md +- Guardrails reviewed: yes +- No-commit run: false +- Commit: 89cd1ab test(desktop): add integration tests for ErrorBanner error handling +- Post-commit status: clean (only .agents/tasks/prd.json modified — expected) +- Verification: + - Command: npm --workspace apps/desktop run test -> PASS (278 tests, 19 files) + - Command: cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml -> PASS (44 tests) + - Command: cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings -> PASS + - Command: npm run web:build -> PASS +- Files changed: + - apps/desktop/src/components/ErrorBanner.test.tsx +- What was implemented: + - Added 13 integration-level tests to ErrorBanner.test.tsx (21 total, 8 existing + 13 new) + - Created ErrorBannerHarness component that mirrors App.tsx error management pattern + - Tests cover: unhandled promise rejection (Error and non-Error), window.error event (with/without error object), auto-clear on successful repo load, multiple rapid errors showing only latest, repo load error propagation, diff compute error propagation, dismiss behavior (clears and doesn't reappear), full recovery flow (error → user action → success), re-render on message change, null transition hiding, global listener cleanup on unmount +- **Learnings for future iterations:** + - ErrorBanner is purely presentational — integration tests require a harness component that replicates the App's useState + useEffect pattern + - Dispatching unhandledrejection in jsdom requires creating a plain Event and manually setting the `reason` property + - window.onerror must be nulled in beforeEach to prevent jsdom from treating dispatched ErrorEvents as real test failures + - The App.tsx error flow: repoStatus.error → useEffect → setErrorMessage → ErrorBanner; also global listeners for unhandled rejections/errors +--- + +## [2026-02-22 14:42] - workspace-lifecycle-tests: Frontend: workspace save/restore lifecycle integration tests +Thread: +Run: 20260222-104122-$ (iteration 22) +Run log: /workspace/.ralph/runs/run-20260222-104122-$-iter-22.log +Run summary: /workspace/.ralph/runs/run-20260222-104122-$-iter-22.md +- Guardrails reviewed: yes +- No-commit run: false +- Commit: 0bb92f5 test(desktop): add workspace save/restore lifecycle integration tests +- Post-commit status: clean +- Verification: + - Command: npm --workspace apps/desktop run test -> PASS (288 tests, 20 files) + - Command: cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml -> PASS (44 tests) + - Command: cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings -> PASS + - Command: npm run web:build -> PASS +- Files changed: + - apps/desktop/src/__tests__/workspaceLifecycle.test.ts (new, 10 tests) +- What was implemented: + - 10 comprehensive integration tests for the full workspace save/restore lifecycle + - Tests cover all acceptance criteria: + 1. Save: captures branch selection (base/compare) and file selection with settings + 2. Restore: loads workspace, restores branches for diff trigger, restores settings + 3. Auto-detect: repo open by path matches saved workspace via findWorkspaceByPath + 4. Refresh: preserves current selection via getWorkspaceSelectionRestore + 5. Delete: removes workspace from store, clears activeWorkspaceId + 6. Missing branch: graceful fallback when saved branch no longer exists + 7. Session auto-persist: debounced settings persist via updateWorkspaceSession + 8. Race condition: workspace switch request ID pattern prevents stale restores + 9. Full lifecycle round-trip: create → save → close → reopen → modify → persist → reload + 10. Multiple workspaces: isolated sessions with no cross-contamination + - Mocked localStorage via vi.stubGlobal with Map-backed store + - Helper functions simulate App.tsx patterns: buildSnapshot, commitWorkspaceStore +- **Learnings for future iterations:** + - Workspace lifecycle logic in App.tsx is tested indirectly through the store functions + - The race condition prevention uses a simple counter (workspaceSwitchRequestRef) that can be simulated in tests + - getWorkspaceSelectionRestore partitions saved paths into matched/missing based on available paths + - upsertWorkspace handles both create and update via path or ID matching +--- + +## [2026-02-22 14:52] - logger-util-tests: Frontend: logger utility tests +Thread: +Run: 20260222-104122-$ (iteration 23) +Run log: /workspace/.ralph/runs/run-20260222-104122-$-iter-23.log +Run summary: /workspace/.ralph/runs/run-20260222-104122-$-iter-23.md +- Guardrails reviewed: yes +- No-commit run: false +- Commit: b3abfde test(desktop): add logger utility unit tests +- Post-commit status: clean (only .agents/tasks/prd.json modified — expected) +- Verification: + - Command: npm --workspace apps/desktop run test -> PASS (294 tests, 21 files) + - Command: cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml -> PASS (44 tests) + - Command: cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings -> PASS + - Command: npm run web:build -> PASS +- Files changed: + - apps/desktop/src/utils/logger.test.ts (new, 6 tests) +- What was implemented: + - Created 6 Vitest tests for the logError function in logger.ts + - Tests cover all acceptance criteria: + 1. Error objects: verifies console.error called with `[source]` prefix and Error object, extracts `.message` for errorLog + 2. String errors: verifies string used directly as the message in errorLog + 3. Non-Error non-string values: objects (String() -> '[object Object]'), null ('null'), undefined ('undefined') + 4. Error code formatting: verifies `[CODE]` prefix in console.error output + 5. console.error mock verifies exact output format for all cases + 6. Bonus: errorLog cap at 100 entries (oldest shifted out) + - Mocked console.error with vi.spyOn to verify output format + - Shared errorLog cleared between tests for isolation +- **Learnings for future iterations:** + - logError uses `String(error)` for non-Error values, which produces '[object Object]' for plain objects + - The errorLog is a module-level mutable array — tests must clear it with `.length = 0` in beforeEach + - console.error is wrapped in try/catch in the source, so it won't throw even if mocked badly + - errorLog has a 100-entry cap with FIFO eviction via shift() +--- + +## [2026-02-22T15:00] - diff-control-bar-comprehensive: Frontend: DiffControlBar comprehensive interaction tests +Thread: +Run: 20260222-104122-$ (iteration 24) +Run log: /workspace/.ralph/runs/run-20260222-104122-$-iter-24.log +Run summary: /workspace/.ralph/runs/run-20260222-104122-$-iter-24.md +- Guardrails reviewed: yes +- No-commit run: false +- Commit: 7789299 test(desktop): add DiffControlBar comprehensive interaction tests +- Post-commit status: clean +- Verification: + - Command: npm --workspace apps/desktop run test -> PASS (307 tests, 21 files) + - Command: cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml -> PASS (44 tests) + - Command: cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings -> PASS + - Command: npm run web:build -> PASS +- Files changed: + - apps/desktop/src/components/DiffControlBar.test.tsx (expanded from 19 to 32 tests) +- What was implemented: + - Added 13 new interaction tests to DiffControlBar.test.tsx for a total of 32 tests + - New tests cover: delete button enable/disable based on workspace selection, save button disabled without path, unsaved workspace label variants, workspace option display format, fallback for unknown workspace IDs, workspace selector title attribute, WORKDIR in base selector, comprehensive enabled/disabled states, disabled prop overriding individual conditions +- **Learnings for future iterations:** + - The existing test file had 19 tests (not 23 as stated in story). The 28 target was still met and exceeded. + - DiffControlBar uses conditional rendering for unsaved/fallback options in the workspace selector + - The workspace title attribute uses selectedWorkspace.path falling back to currentWorkspacePath +--- diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..02df19c --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,62 @@ +# GitContext Agent Instructions + +Purpose: execute one PRD story per loop iteration with high signal, low risk changes. + +## Scope +- Complete only the selected PRD story. Do not expand scope. +- Read `CLAUDE.md` before acting — it is the source of truth for architecture. +- Read relevant code before editing; do not assume missing behavior. +- Keep changes small, deterministic, and reversible. + +## Required Quality Gates +- Every behavior change must include tests (new tests or updates to existing tests). +- Run all available checks before committing: + - Frontend lint: `npm run web:lint` + - Frontend build: `npm run web:build` + - Rust fmt: `cargo fmt --check --manifest-path apps/desktop/src-tauri/Cargo.toml` + - Rust lint: `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings` + - Rust check: `cargo check --manifest-path apps/desktop/src-tauri/Cargo.toml` + - Rust tests: `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml` +- If any required check fails, do not commit. Fix or report the exact failure. + +## Project Commands +- Install deps: `npm install` +- Web dev server: `npm run web:dev` +- Web build: `npm run web:build` +- Web lint: `npm run web:lint` +- Web unit tests: `npm --workspace apps/web run test:unit` +- Web e2e tests: `npm --workspace apps/web run test:e2e` +- Desktop dev: `npm run desktop:dev` (requires macOS host — not available in Docker) +- Desktop build: `npm run desktop:build` (requires macOS host — not available in Docker) +- Rust cargo check: `cargo check --manifest-path apps/desktop/src-tauri/Cargo.toml` +- Rust cargo clippy: `cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings` +- Rust cargo fmt check: `cargo fmt --check --manifest-path apps/desktop/src-tauri/Cargo.toml` +- Rust cargo test: `cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml` + +## Architecture Notes +- This is a Tauri 2.0 + React/Vite monorepo. See `CLAUDE.md` for full details. +- Never call `isomorphic-git` or Tauri commands directly from React components — always go through `GitEngine` in `apps/web/src/platform/gitFactory.ts`. +- Web mode uses isomorphic-git + LightningFS via Web Worker. Desktop mode uses native Rust/libgit2. +- `npm run desktop:*` commands require a real macOS environment (GUI + Xcode toolchain). In Docker, only run `npm run web:*` and `cargo *`. + +## Verification Reporting +- For each story, write a verification report at: + - `.codex/ralph-gitcontext/verify/.md` +- Report must include: + - story id/title + - commands run with pass/fail per command + - files changed + - unresolved risks or follow-ups + - standalone line: `VERIFIED: YES` + +## Commits +- Use semantic commit messages (Conventional Commits): `(): ` +- Allowed types: `feat`, `fix`, `docs`, `chore`, `refactor`, `test`, `ci`, `build`, `perf`, `style`, `revert` +- Only commit when all required checks pass. +- Do NOT include `Co-Authored-By` or authorship lines in commit messages. + +## Safety Rules +- Never commit secrets, credentials, tokens, cookies, or private keys. +- Do not weaken tests or skip checks to make CI pass. +- Do not use destructive git commands (`reset --hard`, forced checkout, history rewrite). +- If requirements conflict or are unclear, stop and document the blocker in the verification report. diff --git a/CHANGELOG.md b/CHANGELOG.md index fdaa849..76833ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,131 @@ +# [2.2.0](https://github.com/kccarlos/gitcontext/compare/v2.1.0...v2.2.0) (2026-02-09) + + +### Bug Fixes + +* resolve TypeScript build errors after removing copy functionality ([cafe86e](https://github.com/kccarlos/gitcontext/commit/cafe86e14d2c1cde6b9f62f84356006621d823b0)) + + +### Features + +* add search clear button and remove Selected Files title ([7708d0e](https://github.com/kccarlos/gitcontext/commit/7708d0e9c122ad6801413228f83e91c9551392ec)) +* implement left panel enhancements ([4b73db4](https://github.com/kccarlos/gitcontext/commit/4b73db484f36152812f08bf1395183024761c87c)) +* implement reveal path functionality and empty state improvements ([aa599d7](https://github.com/kccarlos/gitcontext/commit/aa599d7d9e8158efe9ba479ebd83a656510c476c)) +* implement right panel tabs and context footer improvement ([fcd5dea](https://github.com/kccarlos/gitcontext/commit/fcd5deae43ae1d98d3f81eb39db5cb7bd2a90533)) +* improve Select All/Deselect All to respect filters and use set operations ([cae19fb](https://github.com/kccarlos/gitcontext/commit/cae19fbc991d48374a9d744def15e063ba87ba76)) +* refactor desktop app to CSS Grid layout ([88b7b57](https://github.com/kccarlos/gitcontext/commit/88b7b5727bed238635055ec0b1cc5ffddd708392)) + +# [2.1.0](https://github.com/kccarlos/gitcontext/compare/v2.0.2...v2.1.0) (2026-02-08) + + +### Bug Fixes + +* resolve 3 additional bugs ([448bfc6](https://github.com/kccarlos/gitcontext/commit/448bfc6806624044a8bdb52ac7343cf058e072d2)) +* resolve useEffect dependency order issue in useGitRepository ([9436833](https://github.com/kccarlos/gitcontext/commit/94368336f1c62a0c210bc8a69b7c2a99cf5567c3)) + + +### Features + +* add responsive breakpoints and mobile support ([fca8f99](https://github.com/kccarlos/gitcontext/commit/fca8f99d82bdbd8227eee19003b479965b42cbf7)) +* add working directory support with file watching in desktop app ([2d2e9a6](https://github.com/kccarlos/gitcontext/commit/2d2e9a61094b271c3691cb12b45a229fc5ed8492)) +* implement unified spacing scale and design tokens ([b8bd4ce](https://github.com/kccarlos/gitcontext/commit/b8bd4ce6f1b8996c46a72c19418f82d7e1da2520)) +* move Copy button to fixed footer position ([b7ea6ac](https://github.com/kccarlos/gitcontext/commit/b7ea6acf64be61ff4b9d2cbda08e2774e231cba7)) + +## [2.0.2](https://github.com/kccarlos/gitcontext/compare/v2.0.1...v2.0.2) (2026-02-07) + + +### Bug Fixes + +* **ci:** checkout main branch instead of version tag in deploy-pages ([27ab5d0](https://github.com/kccarlos/gitcontext/commit/27ab5d020d28fc1b626f55a18c67b391b212db79)) +* resolve 10 critical and quality bugs ([bb9bdde](https://github.com/kccarlos/gitcontext/commit/bb9bdde5f8823bdd9bb006a7ed20ea6851617e70)) +* resolve 10 critical and quality bugs ([7286aaf](https://github.com/kccarlos/gitcontext/commit/7286aafbb29c0046c4316103d4899da2a30a6001)) +* resolve 3 additional critical bugs ([101901d](https://github.com/kccarlos/gitcontext/commit/101901d7ea2f586bc9d65fcac42d5eaa56b7bb23)) +* resolve 3 additional critical bugs ([1a8a5f6](https://github.com/kccarlos/gitcontext/commit/1a8a5f6579b02cdfc4cea5358f7978f8d98f0e0a)) + +## [2.0.1](https://github.com/kccarlos/gitcontext/compare/v2.0.0...v2.0.1) (2026-02-07) + + +### Bug Fixes + +* **ci:** checkout main branch instead of version tag in deploy-pages ([856c59a](https://github.com/kccarlos/gitcontext/commit/856c59a1290d191a961373f8559bcfa4f5dc4a71)) + +# [2.0.0](https://github.com/kccarlos/gitcontext/compare/v1.3.2...v2.0.0) (2026-02-07) + + +* feat(desktop)!: migrate from Electron to Tauri (feature parity) ([58e58fe](https://github.com/kccarlos/gitcontext/commit/58e58fe6a544ccc4e0e14b4fb88dbecf5add97b0)) +* feat(desktop)!: migrate from Electron to Tauri with full feature parity ([287ad0a](https://github.com/kccarlos/gitcontext/commit/287ad0a7b0d57a17b28389d6e6c54868bec20385)) + + +### Bug Fixes + +* **ci:** update CI workflow to use new monorepo structure and Tauri ([93908fe](https://github.com/kccarlos/gitcontext/commit/93908feefed44cee3f27f9aa5c35d317ce9a96f8)) +* regenerate package-lock.json to sync with package.json ([3264e1c](https://github.com/kccarlos/gitcontext/commit/3264e1cd7e7fc561301283419b99782e084299d5)) + + +### Features + +* **core:** add shared core package with GitService interface and migrate utilities ([2ec0779](https://github.com/kccarlos/gitcontext/commit/2ec0779654b9e86a2c7353267b7b44726fd6594d)) +* **desktop/tauri:** add native git backend via git2 ([d54bde8](https://github.com/kccarlos/gitcontext/commit/d54bde8f000bfcea4da1f5a7a6a02267d240ab90)) +* **ui:** extract shared React components into @gitcontext/ui ([241c0cc](https://github.com/kccarlos/gitcontext/commit/241c0ccfef1d167512f448fc4484648aac00f26b)) + + +### Performance Improvements + +* **workdir:** add guardrails, route reads to main thread, and improve concurrency ([b794563](https://github.com/kccarlos/gitcontext/commit/b794563261237ceabd7f5b64374a0e1be11bf4f1)) +* **workdir:** improve performance ([7387ec4](https://github.com/kccarlos/gitcontext/commit/7387ec4a3a7c1e7a4e5e211673d1465b5a143106)) +* **workdir:** performance & scalability for very large repos ([7c51162](https://github.com/kccarlos/gitcontext/commit/7c51162709a969a61f47e19d200c8cca925d53b8)) +* **workdir:** remove eager snapshotting and compute WORKDIR diffs lazily ([2139e7b](https://github.com/kccarlos/gitcontext/commit/2139e7b055b4cfd72a2a9003fd5734d95ed2a719)) + + +### BREAKING CHANGES + +* Desktop app now uses Tauri instead of Electron + +- Replace Electron with Tauri 2.0 framework for native desktop app +- Implement native Rust backend using git2 crate for Git operations +- Remove all Electron code (src/electron/, electron-builder.yml) +- Add complete feature parity with web app: + * Token counting with tiktoken WASM integration + * File tree with filtering and selection + * Diff generation and preview + * Context lines slider + * Clipboard integration via tauri-plugin-clipboard-manager + * Dark mode with proper CSS variable theming +- Add landing page with "How it works" guide +- Add clickable logo at top left (returns to landing) +- Add GitHub repository links (Star on GitHub, Report a Bug) +- Add refresh button to update file tree while preserving selection +- Add "Include binary files as paths" checkbox +- Fix file tree auto-expansion for directories with changed files +- Fix double-slash path bug in Rust git2 tree walking +- Update GitHub Actions workflow for Tauri builds (macOS, Windows, Linux) +- Update README to reflect new Tauri architecture +- Remove 245 Electron-related npm packages +- Merge with workdir performance improvements from main branch +- Maintain all lazy loading, caching, and main-thread file reading optimizations +* Desktop app now uses Tauri instead of Electron. + +- Replace Electron with Tauri 2.0 for the native desktop app. +- Add Rust backend (git2) for Git operations. +- Remove Electron code and tooling (src/electron/, electron-builder.yml). +- Reach feature parity with web: + - Token counting (tiktoken WASM). + - File tree filtering/selection + refresh (preserve selection). + - Diff generation/preview + context lines slider. + - Clipboard via tauri-plugin-clipboard-manager. + - Dark mode with CSS variable theming. + - Include binaries as paths option. +- UX/content: + - Landing page with "How it works" + clickable logo to return. + - GitHub links (Star, Report a Bug). +- Fixes: + - File tree auto-expansion for changed directories. + - Double-slash path issue in Rust git2 tree walking. +- CI/docs: + - Update GitHub Actions for Tauri builds (macOS/Windows/Linux). + - Update README for Tauri architecture. +- Cleanup: remove 245 Electron-related npm packages. + ## [1.3.2](https://github.com/kccarlos/gitcontext/compare/v1.3.1...v1.3.2) (2026-01-28) diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..1e5379c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,262 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Overview + +GitContext is a privacy-first, browser-based tool for analyzing Git repositories and packaging code/diffs into LLM-friendly format. It runs as both a web app (React + Vite) and desktop app (Tauri 2.0), using isomorphic-git for in-browser Git operations. + +**Key architectural principle**: Zero server dependency. All Git operations run client-side using isomorphic-git + LightningFS (in browser) or native Rust git operations via libgit2 (in Tauri desktop). + +## Build and Development Commands + +### Web App +```bash +# Development +npm run web:dev # Start dev server on localhost:5173 + +# Build +npm run web:build # Production build +npm run web:preview # Preview production build + +# Testing +npm --workspace apps/web run test:e2e # Run Playwright e2e tests +npm --workspace apps/web run test:unit # Run Vitest unit tests +npm --workspace apps/web run test # Run all tests +``` + +### Tauri Desktop App +```bash +# Development +npm run desktop:dev # Start Tauri with hot reload + +# Build +npm run desktop:build # Package desktop app for current platform + +# Rust development +cd apps/desktop/src-tauri +cargo build # Build Rust backend +cargo test # Run Rust tests +``` + +### Linting +```bash +npm run lint # Lint all workspaces +npm run web:lint # Lint web workspace only +``` + +## Architecture + +### Dual Runtime Strategy + +GitContext supports two execution modes determined at runtime: + +1. **Web Mode** (Browser): Uses `isomorphic-git` + `LightningFS` for in-memory file system, all Git operations via Web Worker (`apps/web/src/workers/gitWorker.ts`) +2. **Desktop Mode** (Tauri): Uses native Rust Git operations via Tauri commands (`apps/desktop/src-tauri/src/git.rs` using libgit2) + +The mode is abstracted via `apps/web/src/platform/gitFactory.ts` which returns a `GitEngine` interface. The app code doesn't distinguish between modes. + +### Core Flow + +1. **Repository Loading** (`apps/web/src/hooks/useGitRepository.ts`): + - User selects directory via File System Access API (web) or native dialog (desktop) + - For web: snapshots `.git` directory contents and passes to worker + - Worker initializes LightningFS, seeds with Git objects, lists branches + - Desktop: Tauri commands directly use native filesystem and libgit2 + +2. **Diff Computation** (`apps/web/src/hooks/useFileTree.ts`): + - Computes name-status diff between two branches via `GitEngine.diff()` + - Builds file tree with status markers (add/modify/remove/unchanged/rename/copy) + - Supports filtering to changed files only + - Race condition prevention with request ID tracking + +3. **File Reading** (`apps/web/src/utils/diff.ts`): + - Reads file content at specific refs via `GitEngine.readFile()` + - Binary detection via extension patterns (`@gitcontext/core` package) + - Generates unified diffs using `diff` library + - Handles non-UTF8 files with lossy conversion (Rust backend) + +4. **Token Counting** (`apps/web/src/hooks/useTokenCounts.ts`): + - Uses `@dqbd/tiktoken` (cl100k_base encoding) for GPT-4/3.5 token estimation + - Debounced counting on selection changes with cancelation support + - Bounded concurrency for efficient batch processing + - Matches exact output format for accuracy + +5. **Output Generation** (`apps/web/src/App.tsx`): + - Aggregates selected files/diffs into single text blob + - Includes file tree structure, model context windows + - One-click copy to clipboard + - Template system for custom instructions + +### Key Directories + +- `apps/web/` - Web application + - `src/components/` - UI components (file tree, preview modal, status bar, etc.) + - `src/hooks/` - Core business logic hooks (useGitRepository, useFileTree, useWorkspaces, useTokenCounts) + - `src/utils/` - Pure utility functions (diff, tokenizer, binary detection, gitWorkerClient) + - `src/platform/` - Abstraction layer for web vs desktop (`gitFactory.ts`, `tokenizerFactory.ts`) + - `src/workers/` - Web Worker for Git operations (`gitWorker.ts`) + - `src/types/` - TypeScript type definitions + - `src/context/` - React Context providers + +- `apps/desktop/` - Tauri desktop application + - `src/` - Frontend code (shares UI with web app) + - `src-tauri/src/` - Rust backend + - `git.rs` - Git operations using libgit2 + - `main.rs` - Tauri main entry point + - `lib.rs` - Tauri command registration + +- `packages/core/` - Shared utilities + - Binary file detection + - Constants and types + +### Worker Communication Pattern + +Web Worker API (`apps/web/src/utils/gitWorkerClient.ts`): +```typescript +type RequestMessage = { id: number; type: 'loadRepo' | 'diff' | 'readFile' | ... } +type ResponseMessage = + | { id: number; type: 'ok'; data: any } + | { id: number; type: 'error'; error: string } + | { id: number; type: 'progress'; message: string } +``` + +Messages are request-response paired by `id`. Progress messages stream without resolving the request. + +### File System Access + +**Web**: Uses File System Access API (`apps/web/src/utils/fs.ts`): +- `pickDirectory()` - Prompts user for directory handle +- `ensurePermission()` - Requests read permission +- `snapshotGitFiles()` - Recursively reads `.git` directory into memory +- `snapshotWorktreeFiles()` - Reads working tree for WORKDIR support + +**Desktop**: Native filesystem via Tauri commands, no snapshots needed. Uses Rust file I/O with libgit2. + +### Persistence + +- **Workspace History** (`src/web/src/hooks/useWorkspaces.ts`): Stores FileSystemDirectoryHandle references in IndexedDB for quick repo reopening (web only, requires File System Access API) +- **Branch Selection**: Persisted per-repo in localStorage +- **Theme Preference**: Stored in localStorage + +## Important Patterns + +### Binary File Handling + +Binary files are detected by extension patterns (`packages/core/src/binary.ts`). When detected: +- File tree marks them with `isLikelyBinary: true` +- Preview shows "[Binary file]" placeholder +- Output includes binary notice instead of content +- First 8KB can be sniffed for content-based detection + +### Token Counting + +Token counting is debounced (300ms) to avoid blocking UI during selection changes. The counting happens in main thread (not worker) because tiktoken WASM module is easier to load there. Token counts match exact output format for accuracy. + +### Error Handling + +Errors are logged via `apps/web/src/utils/logger.ts` which wraps console with error codes for easier debugging. User-facing errors set `appStatus` state with error messages shown in StatusBar. Effects handle errors with logging instead of throwing to prevent unhandled rejections. + +### Git Operations Abstraction + +Never call `isomorphic-git` or Tauri commands directly from React components. Always use: +1. `GitEngine` interface from `gitFactory.ts` +2. Hook methods like `gitClient.diff()`, `gitClient.readFile()` +3. This allows desktop/web modes to swap implementations transparently + +### Race Condition Prevention + +Async operations that can be triggered multiple times use request ID tracking to ignore stale results: +- Preview file operations (`previewRequestIdRef`) +- Diff computation (`diffRequestIdRef`) +- Token counting with abort controller + +### Design Token System + +The app uses CSS custom properties for consistent spacing, sizing, and colors: +- **Spacing scale**: `--space-0` through `--space-12` (8px base unit) +- **Border radius**: `--radius-sm` through `--radius-xl` +- **Font sizes**: `--text-xs` through `--text-2xl` +- **Colors**: `--primary`, `--surface-1`, `--surface-2`, `--border-col` + +All hardcoded spacing/sizing values should use these tokens for maintainability. + +## Testing + +- **E2E Tests**: Playwright tests in `apps/web/tests/e2e/` - test actual file system operations and Git workflows +- **Unit Tests**: Vitest tests for utilities (when present) +- **Rust Tests**: Cargo tests in `apps/desktop/src-tauri/src/` - test Git operations +- Tests run in web mode by default + +## Build Process + +### Web Build +1. Vite bundles React app with Node.js polyfills (buffer, process, stream, path) +2. Worker bundled separately as ES module via `worker.format: 'es'` +3. tiktoken excluded from optimizeDeps to preserve WASM handling + +### Tauri Desktop Build +1. Frontend: Vite builds with `TAURI=1` flag +2. Backend: Cargo compiles Rust code with libgit2 +3. Tauri CLI bundles frontend + backend together +4. Output: DMG (macOS), NSIS installer (Windows), AppImage (Linux) + +### Release Pipeline +Semantic-release on main branch: +1. `build-desktop` job builds all 3 platforms in parallel using Tauri +2. `release` job runs semantic-release, creates GitHub release with binaries +3. `deploy-pages` job builds web app and deploys to GitHub Pages + +Version bumps follow conventional commits (fix/feat/BREAKING CHANGE). + +## Development Notes + +- **Monorepo Structure**: NPM workspaces with `apps/web`, `apps/desktop`, `packages/core` +- **Vite Config**: `apps/web/vite.config.ts` configures conditional Tauri plugins, Node polyfills, WASM support +- **File System Access API**: Only available in secure contexts (HTTPS or localhost). Degrades gracefully if unavailable. +- **LightningFS Caching**: Git objects stored in IndexedDB for fast reloads (web mode) +- **Rust Backend**: libgit2 provides native Git operations with better performance than isomorphic-git + +## Git Workflow + +### Branch Strategy +- **dev**: Main development branch - all changes go here first +- **main**: Production branch - only receives merges from dev after testing +- Always push to `dev` first, then merge to `main` after verification + +### Commit Message Format +Follow conventional commits (semantic git): +- `feat: description` - New features +- `fix: description` - Bug fixes +- `refactor: description` - Code refactoring +- `chore: description` - Maintenance tasks +- `docs: description` - Documentation changes +- `test: description` - Test changes +- `style: description` - Code style changes (formatting, etc.) + +**IMPORTANT**: Do NOT include authorship information (like `Co-Authored-By`) in commit messages. Let Git handle authorship automatically. + +### Commit Best Practices +- Write clear, concise commit messages +- Focus on the "why" rather than the "what" +- Keep commits atomic and focused on a single change +- Use imperative mood ("add feature" not "added feature") +- Include context in the body if the change needs explanation + +Example good commit: +``` +feat: implement unified spacing scale and design tokens + +- Add comprehensive design token system to both web and desktop +- Convert all hardcoded spacing/sizing values to use tokens +- Ensures visual consistency across the application +``` + +## Code Style + +- TypeScript strict mode enabled +- React functional components with hooks (no class components) +- Prefer explicit types over inference for public APIs +- Use `useCallback` for functions passed to children to avoid re-renders +- Worker messages must include `id` for request-response pairing +- Use design tokens for all spacing, sizing, and color values diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml index 8759690..07429f3 100644 --- a/apps/desktop/src-tauri/Cargo.toml +++ b/apps/desktop/src-tauri/Cargo.toml @@ -24,5 +24,8 @@ ignore = "0.4" notify = "6.1" notify-debouncer-full = "0.3" +[dev-dependencies] +tempfile = "3" + [target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies] tauri-plugin-cli = "2.0" diff --git a/apps/desktop/src-tauri/gen/schemas/linux-schema.json b/apps/desktop/src-tauri/gen/schemas/linux-schema.json new file mode 100644 index 0000000..32e5a22 --- /dev/null +++ b/apps/desktop/src-tauri/gen/schemas/linux-schema.json @@ -0,0 +1,5934 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CapabilityFile", + "description": "Capability formats accepted in a capability file.", + "anyOf": [ + { + "description": "A single capability.", + "allOf": [ + { + "$ref": "#/definitions/Capability" + } + ] + }, + { + "description": "A list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + }, + { + "description": "A list of capabilities.", + "type": "object", + "required": [ + "capabilities" + ], + "properties": { + "capabilities": { + "description": "The list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + } + } + } + ], + "definitions": { + "Capability": { + "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows' and webviews' fine grained access to the Tauri core, application, or plugin commands. If a webview or its window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```", + "type": "object", + "required": [ + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "Identifier of the capability.\n\n## Example\n\n`main-user-files-write`", + "type": "string" + }, + "description": { + "description": "Description of what the capability is intended to allow on associated windows.\n\nIt should contain a description of what the grouped permissions should allow.\n\n## Example\n\nThis capability allows the `main` window access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.", + "default": "", + "type": "string" + }, + "remote": { + "description": "Configure remote URLs that can use the capability permissions.\n\nThis setting is optional and defaults to not being set, as our default use case is that the content is served from our local application.\n\n:::caution Make sure you understand the security implications of providing remote sources with local system access. :::\n\n## Example\n\n```json { \"urls\": [\"https://*.mydomain.dev\"] } ```", + "anyOf": [ + { + "$ref": "#/definitions/CapabilityRemote" + }, + { + "type": "null" + } + ] + }, + "local": { + "description": "Whether this capability is enabled for local app URLs or not. Defaults to `true`.", + "default": true, + "type": "boolean" + }, + "windows": { + "description": "List of windows that are affected by this capability. Can be a glob pattern.\n\nIf a window label matches any of the patterns in this list, the capability will be enabled on all the webviews of that window, regardless of the value of [`Self::webviews`].\n\nOn multiwebview windows, prefer specifying [`Self::webviews`] and omitting [`Self::windows`] for a fine grained access control.\n\n## Example\n\n`[\"main\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "webviews": { + "description": "List of webviews that are affected by this capability. Can be a glob pattern.\n\nThe capability will be enabled on all the webviews whose label matches any of the patterns in this list, regardless of whether the webview's window label matches a pattern in [`Self::windows`].\n\n## Example\n\n`[\"sub-webview-one\", \"sub-webview-two\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "permissions": { + "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionEntry" + }, + "uniqueItems": true + }, + "platforms": { + "description": "Limit which target platforms this capability applies to.\n\nBy default all platforms are targeted.\n\n## Example\n\n`[\"macOS\",\"windows\"]`", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "CapabilityRemote": { + "description": "Configuration for remote URLs that are associated with the capability.", + "type": "object", + "required": [ + "urls" + ], + "properties": { + "urls": { + "description": "Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).\n\n## Examples\n\n- \"https://*.mydomain.dev\": allows subdomains of mydomain.dev - \"https://mydomain.dev/api/*\": allows any subpath of mydomain.dev/api", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionEntry": { + "description": "An entry for a permission value in a [`Capability`] can be either a raw permission [`Identifier`] or an object that references a permission and extends its scope.", + "anyOf": [ + { + "description": "Reference a permission or permission set by identifier.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + { + "description": "Reference a permission or permission set by identifier and extends its scope.", + "type": "object", + "allOf": [ + { + "if": { + "properties": { + "identifier": { + "anyOf": [ + { + "description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### This default permission set includes:\n\n- `create-app-specific-dirs`\n- `read-app-specific-dirs-recursive`\n- `deny-default`", + "type": "string", + "const": "fs:default", + "markdownDescription": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### This default permission set includes:\n\n- `create-app-specific-dirs`\n- `read-app-specific-dirs-recursive`\n- `deny-default`" + }, + { + "description": "This allows non-recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-index`", + "type": "string", + "const": "fs:allow-app-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-index`" + }, + { + "description": "This allows full recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-recursive`", + "type": "string", + "const": "fs:allow-app-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive read access to the application folders.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app`", + "type": "string", + "const": "fs:allow-app-read", + "markdownDescription": "This allows non-recursive read access to the application folders.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app`" + }, + { + "description": "This allows full recursive read access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app-recursive`", + "type": "string", + "const": "fs:allow-app-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive write access to the application folders.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app`", + "type": "string", + "const": "fs:allow-app-write", + "markdownDescription": "This allows non-recursive write access to the application folders.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app`" + }, + { + "description": "This allows full recursive write access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app-recursive`", + "type": "string", + "const": "fs:allow-app-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-index`", + "type": "string", + "const": "fs:allow-appcache-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-recursive`", + "type": "string", + "const": "fs:allow-appcache-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache`", + "type": "string", + "const": "fs:allow-appcache-read", + "markdownDescription": "This allows non-recursive read access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache`" + }, + { + "description": "This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache-recursive`", + "type": "string", + "const": "fs:allow-appcache-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache`", + "type": "string", + "const": "fs:allow-appcache-write", + "markdownDescription": "This allows non-recursive write access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache`" + }, + { + "description": "This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache-recursive`", + "type": "string", + "const": "fs:allow-appcache-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-index`", + "type": "string", + "const": "fs:allow-appconfig-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "fs:allow-appconfig-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig`", + "type": "string", + "const": "fs:allow-appconfig-read", + "markdownDescription": "This allows non-recursive read access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig`" + }, + { + "description": "This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "fs:allow-appconfig-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig`", + "type": "string", + "const": "fs:allow-appconfig-write", + "markdownDescription": "This allows non-recursive write access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig`" + }, + { + "description": "This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "fs:allow-appconfig-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-index`", + "type": "string", + "const": "fs:allow-appdata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-recursive`", + "type": "string", + "const": "fs:allow-appdata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata`", + "type": "string", + "const": "fs:allow-appdata-read", + "markdownDescription": "This allows non-recursive read access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata`" + }, + { + "description": "This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata-recursive`", + "type": "string", + "const": "fs:allow-appdata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata`", + "type": "string", + "const": "fs:allow-appdata-write", + "markdownDescription": "This allows non-recursive write access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata`" + }, + { + "description": "This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata-recursive`", + "type": "string", + "const": "fs:allow-appdata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-index`", + "type": "string", + "const": "fs:allow-applocaldata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "fs:allow-applocaldata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata`", + "type": "string", + "const": "fs:allow-applocaldata-read", + "markdownDescription": "This allows non-recursive read access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata`" + }, + { + "description": "This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "fs:allow-applocaldata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata`", + "type": "string", + "const": "fs:allow-applocaldata-write", + "markdownDescription": "This allows non-recursive write access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata`" + }, + { + "description": "This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "fs:allow-applocaldata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-index`", + "type": "string", + "const": "fs:allow-applog-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-recursive`", + "type": "string", + "const": "fs:allow-applog-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog`", + "type": "string", + "const": "fs:allow-applog-read", + "markdownDescription": "This allows non-recursive read access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog`" + }, + { + "description": "This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog-recursive`", + "type": "string", + "const": "fs:allow-applog-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog`", + "type": "string", + "const": "fs:allow-applog-write", + "markdownDescription": "This allows non-recursive write access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog`" + }, + { + "description": "This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog-recursive`", + "type": "string", + "const": "fs:allow-applog-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-index`", + "type": "string", + "const": "fs:allow-audio-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-recursive`", + "type": "string", + "const": "fs:allow-audio-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio`", + "type": "string", + "const": "fs:allow-audio-read", + "markdownDescription": "This allows non-recursive read access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio`" + }, + { + "description": "This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio-recursive`", + "type": "string", + "const": "fs:allow-audio-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio`", + "type": "string", + "const": "fs:allow-audio-write", + "markdownDescription": "This allows non-recursive write access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio`" + }, + { + "description": "This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio-recursive`", + "type": "string", + "const": "fs:allow-audio-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-index`", + "type": "string", + "const": "fs:allow-cache-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-recursive`", + "type": "string", + "const": "fs:allow-cache-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache`", + "type": "string", + "const": "fs:allow-cache-read", + "markdownDescription": "This allows non-recursive read access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache`" + }, + { + "description": "This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache-recursive`", + "type": "string", + "const": "fs:allow-cache-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache`", + "type": "string", + "const": "fs:allow-cache-write", + "markdownDescription": "This allows non-recursive write access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache`" + }, + { + "description": "This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache-recursive`", + "type": "string", + "const": "fs:allow-cache-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-index`", + "type": "string", + "const": "fs:allow-config-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-recursive`", + "type": "string", + "const": "fs:allow-config-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config`", + "type": "string", + "const": "fs:allow-config-read", + "markdownDescription": "This allows non-recursive read access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config`" + }, + { + "description": "This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config-recursive`", + "type": "string", + "const": "fs:allow-config-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config`", + "type": "string", + "const": "fs:allow-config-write", + "markdownDescription": "This allows non-recursive write access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config`" + }, + { + "description": "This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config-recursive`", + "type": "string", + "const": "fs:allow-config-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-index`", + "type": "string", + "const": "fs:allow-data-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-recursive`", + "type": "string", + "const": "fs:allow-data-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data`", + "type": "string", + "const": "fs:allow-data-read", + "markdownDescription": "This allows non-recursive read access to the `$DATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data`" + }, + { + "description": "This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data-recursive`", + "type": "string", + "const": "fs:allow-data-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data`", + "type": "string", + "const": "fs:allow-data-write", + "markdownDescription": "This allows non-recursive write access to the `$DATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data`" + }, + { + "description": "This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data-recursive`", + "type": "string", + "const": "fs:allow-data-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-index`", + "type": "string", + "const": "fs:allow-desktop-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-recursive`", + "type": "string", + "const": "fs:allow-desktop-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop`", + "type": "string", + "const": "fs:allow-desktop-read", + "markdownDescription": "This allows non-recursive read access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop`" + }, + { + "description": "This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop-recursive`", + "type": "string", + "const": "fs:allow-desktop-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop`", + "type": "string", + "const": "fs:allow-desktop-write", + "markdownDescription": "This allows non-recursive write access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop`" + }, + { + "description": "This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop-recursive`", + "type": "string", + "const": "fs:allow-desktop-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-index`", + "type": "string", + "const": "fs:allow-document-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-recursive`", + "type": "string", + "const": "fs:allow-document-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document`", + "type": "string", + "const": "fs:allow-document-read", + "markdownDescription": "This allows non-recursive read access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document`" + }, + { + "description": "This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document-recursive`", + "type": "string", + "const": "fs:allow-document-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document`", + "type": "string", + "const": "fs:allow-document-write", + "markdownDescription": "This allows non-recursive write access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document`" + }, + { + "description": "This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document-recursive`", + "type": "string", + "const": "fs:allow-document-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-index`", + "type": "string", + "const": "fs:allow-download-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-recursive`", + "type": "string", + "const": "fs:allow-download-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download`", + "type": "string", + "const": "fs:allow-download-read", + "markdownDescription": "This allows non-recursive read access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download`" + }, + { + "description": "This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download-recursive`", + "type": "string", + "const": "fs:allow-download-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download`", + "type": "string", + "const": "fs:allow-download-write", + "markdownDescription": "This allows non-recursive write access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download`" + }, + { + "description": "This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download-recursive`", + "type": "string", + "const": "fs:allow-download-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-index`", + "type": "string", + "const": "fs:allow-exe-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-recursive`", + "type": "string", + "const": "fs:allow-exe-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$EXE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe`", + "type": "string", + "const": "fs:allow-exe-read", + "markdownDescription": "This allows non-recursive read access to the `$EXE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe`" + }, + { + "description": "This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe-recursive`", + "type": "string", + "const": "fs:allow-exe-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$EXE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe`", + "type": "string", + "const": "fs:allow-exe-write", + "markdownDescription": "This allows non-recursive write access to the `$EXE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe`" + }, + { + "description": "This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe-recursive`", + "type": "string", + "const": "fs:allow-exe-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-index`", + "type": "string", + "const": "fs:allow-font-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-recursive`", + "type": "string", + "const": "fs:allow-font-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$FONT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font`", + "type": "string", + "const": "fs:allow-font-read", + "markdownDescription": "This allows non-recursive read access to the `$FONT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font`" + }, + { + "description": "This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font-recursive`", + "type": "string", + "const": "fs:allow-font-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$FONT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font`", + "type": "string", + "const": "fs:allow-font-write", + "markdownDescription": "This allows non-recursive write access to the `$FONT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font`" + }, + { + "description": "This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font-recursive`", + "type": "string", + "const": "fs:allow-font-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-index`", + "type": "string", + "const": "fs:allow-home-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-recursive`", + "type": "string", + "const": "fs:allow-home-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$HOME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home`", + "type": "string", + "const": "fs:allow-home-read", + "markdownDescription": "This allows non-recursive read access to the `$HOME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home`" + }, + { + "description": "This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home-recursive`", + "type": "string", + "const": "fs:allow-home-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$HOME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home`", + "type": "string", + "const": "fs:allow-home-write", + "markdownDescription": "This allows non-recursive write access to the `$HOME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home`" + }, + { + "description": "This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home-recursive`", + "type": "string", + "const": "fs:allow-home-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-index`", + "type": "string", + "const": "fs:allow-localdata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-recursive`", + "type": "string", + "const": "fs:allow-localdata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata`", + "type": "string", + "const": "fs:allow-localdata-read", + "markdownDescription": "This allows non-recursive read access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata`" + }, + { + "description": "This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata-recursive`", + "type": "string", + "const": "fs:allow-localdata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata`", + "type": "string", + "const": "fs:allow-localdata-write", + "markdownDescription": "This allows non-recursive write access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata`" + }, + { + "description": "This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata-recursive`", + "type": "string", + "const": "fs:allow-localdata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-index`", + "type": "string", + "const": "fs:allow-log-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-recursive`", + "type": "string", + "const": "fs:allow-log-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$LOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log`", + "type": "string", + "const": "fs:allow-log-read", + "markdownDescription": "This allows non-recursive read access to the `$LOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log`" + }, + { + "description": "This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log-recursive`", + "type": "string", + "const": "fs:allow-log-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$LOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log`", + "type": "string", + "const": "fs:allow-log-write", + "markdownDescription": "This allows non-recursive write access to the `$LOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log`" + }, + { + "description": "This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log-recursive`", + "type": "string", + "const": "fs:allow-log-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-index`", + "type": "string", + "const": "fs:allow-picture-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-recursive`", + "type": "string", + "const": "fs:allow-picture-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture`", + "type": "string", + "const": "fs:allow-picture-read", + "markdownDescription": "This allows non-recursive read access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture`" + }, + { + "description": "This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture-recursive`", + "type": "string", + "const": "fs:allow-picture-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture`", + "type": "string", + "const": "fs:allow-picture-write", + "markdownDescription": "This allows non-recursive write access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture`" + }, + { + "description": "This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture-recursive`", + "type": "string", + "const": "fs:allow-picture-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-index`", + "type": "string", + "const": "fs:allow-public-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-recursive`", + "type": "string", + "const": "fs:allow-public-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public`", + "type": "string", + "const": "fs:allow-public-read", + "markdownDescription": "This allows non-recursive read access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public`" + }, + { + "description": "This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public-recursive`", + "type": "string", + "const": "fs:allow-public-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public`", + "type": "string", + "const": "fs:allow-public-write", + "markdownDescription": "This allows non-recursive write access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public`" + }, + { + "description": "This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public-recursive`", + "type": "string", + "const": "fs:allow-public-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-index`", + "type": "string", + "const": "fs:allow-resource-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-recursive`", + "type": "string", + "const": "fs:allow-resource-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource`", + "type": "string", + "const": "fs:allow-resource-read", + "markdownDescription": "This allows non-recursive read access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource`" + }, + { + "description": "This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource-recursive`", + "type": "string", + "const": "fs:allow-resource-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource`", + "type": "string", + "const": "fs:allow-resource-write", + "markdownDescription": "This allows non-recursive write access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource`" + }, + { + "description": "This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource-recursive`", + "type": "string", + "const": "fs:allow-resource-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-index`", + "type": "string", + "const": "fs:allow-runtime-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-recursive`", + "type": "string", + "const": "fs:allow-runtime-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime`", + "type": "string", + "const": "fs:allow-runtime-read", + "markdownDescription": "This allows non-recursive read access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime`" + }, + { + "description": "This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime-recursive`", + "type": "string", + "const": "fs:allow-runtime-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime`", + "type": "string", + "const": "fs:allow-runtime-write", + "markdownDescription": "This allows non-recursive write access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime`" + }, + { + "description": "This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime-recursive`", + "type": "string", + "const": "fs:allow-runtime-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-index`", + "type": "string", + "const": "fs:allow-temp-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-recursive`", + "type": "string", + "const": "fs:allow-temp-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp`", + "type": "string", + "const": "fs:allow-temp-read", + "markdownDescription": "This allows non-recursive read access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp`" + }, + { + "description": "This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp-recursive`", + "type": "string", + "const": "fs:allow-temp-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp`", + "type": "string", + "const": "fs:allow-temp-write", + "markdownDescription": "This allows non-recursive write access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp`" + }, + { + "description": "This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp-recursive`", + "type": "string", + "const": "fs:allow-temp-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-index`", + "type": "string", + "const": "fs:allow-template-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-recursive`", + "type": "string", + "const": "fs:allow-template-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template`", + "type": "string", + "const": "fs:allow-template-read", + "markdownDescription": "This allows non-recursive read access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template`" + }, + { + "description": "This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template-recursive`", + "type": "string", + "const": "fs:allow-template-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template`", + "type": "string", + "const": "fs:allow-template-write", + "markdownDescription": "This allows non-recursive write access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template`" + }, + { + "description": "This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template-recursive`", + "type": "string", + "const": "fs:allow-template-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-index`", + "type": "string", + "const": "fs:allow-video-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-recursive`", + "type": "string", + "const": "fs:allow-video-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video`", + "type": "string", + "const": "fs:allow-video-read", + "markdownDescription": "This allows non-recursive read access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video`" + }, + { + "description": "This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video-recursive`", + "type": "string", + "const": "fs:allow-video-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video`", + "type": "string", + "const": "fs:allow-video-write", + "markdownDescription": "This allows non-recursive write access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video`" + }, + { + "description": "This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video-recursive`", + "type": "string", + "const": "fs:allow-video-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video-recursive`" + }, + { + "description": "This denies access to dangerous Tauri relevant files and folders by default.\n#### This permission set includes:\n\n- `deny-webview-data-linux`\n- `deny-webview-data-windows`", + "type": "string", + "const": "fs:deny-default", + "markdownDescription": "This denies access to dangerous Tauri relevant files and folders by default.\n#### This permission set includes:\n\n- `deny-webview-data-linux`\n- `deny-webview-data-windows`" + }, + { + "description": "Enables the copy_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-copy-file", + "markdownDescription": "Enables the copy_file command without any pre-configured scope." + }, + { + "description": "Enables the create command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-create", + "markdownDescription": "Enables the create command without any pre-configured scope." + }, + { + "description": "Enables the exists command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-exists", + "markdownDescription": "Enables the exists command without any pre-configured scope." + }, + { + "description": "Enables the fstat command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-fstat", + "markdownDescription": "Enables the fstat command without any pre-configured scope." + }, + { + "description": "Enables the ftruncate command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-ftruncate", + "markdownDescription": "Enables the ftruncate command without any pre-configured scope." + }, + { + "description": "Enables the lstat command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-lstat", + "markdownDescription": "Enables the lstat command without any pre-configured scope." + }, + { + "description": "Enables the mkdir command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-mkdir", + "markdownDescription": "Enables the mkdir command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Enables the read command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read", + "markdownDescription": "Enables the read command without any pre-configured scope." + }, + { + "description": "Enables the read_dir command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-dir", + "markdownDescription": "Enables the read_dir command without any pre-configured scope." + }, + { + "description": "Enables the read_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-file", + "markdownDescription": "Enables the read_file command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-text-file", + "markdownDescription": "Enables the read_text_file command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file_lines command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-text-file-lines", + "markdownDescription": "Enables the read_text_file_lines command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file_lines_next command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-text-file-lines-next", + "markdownDescription": "Enables the read_text_file_lines_next command without any pre-configured scope." + }, + { + "description": "Enables the remove command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-remove", + "markdownDescription": "Enables the remove command without any pre-configured scope." + }, + { + "description": "Enables the rename command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-rename", + "markdownDescription": "Enables the rename command without any pre-configured scope." + }, + { + "description": "Enables the seek command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-seek", + "markdownDescription": "Enables the seek command without any pre-configured scope." + }, + { + "description": "Enables the size command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-size", + "markdownDescription": "Enables the size command without any pre-configured scope." + }, + { + "description": "Enables the stat command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-stat", + "markdownDescription": "Enables the stat command without any pre-configured scope." + }, + { + "description": "Enables the truncate command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-truncate", + "markdownDescription": "Enables the truncate command without any pre-configured scope." + }, + { + "description": "Enables the unwatch command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-unwatch", + "markdownDescription": "Enables the unwatch command without any pre-configured scope." + }, + { + "description": "Enables the watch command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-watch", + "markdownDescription": "Enables the watch command without any pre-configured scope." + }, + { + "description": "Enables the write command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-write", + "markdownDescription": "Enables the write command without any pre-configured scope." + }, + { + "description": "Enables the write_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-write-file", + "markdownDescription": "Enables the write_file command without any pre-configured scope." + }, + { + "description": "Enables the write_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-write-text-file", + "markdownDescription": "Enables the write_text_file command without any pre-configured scope." + }, + { + "description": "This permissions allows to create the application specific directories.\n", + "type": "string", + "const": "fs:create-app-specific-dirs", + "markdownDescription": "This permissions allows to create the application specific directories.\n" + }, + { + "description": "Denies the copy_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-copy-file", + "markdownDescription": "Denies the copy_file command without any pre-configured scope." + }, + { + "description": "Denies the create command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-create", + "markdownDescription": "Denies the create command without any pre-configured scope." + }, + { + "description": "Denies the exists command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-exists", + "markdownDescription": "Denies the exists command without any pre-configured scope." + }, + { + "description": "Denies the fstat command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-fstat", + "markdownDescription": "Denies the fstat command without any pre-configured scope." + }, + { + "description": "Denies the ftruncate command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-ftruncate", + "markdownDescription": "Denies the ftruncate command without any pre-configured scope." + }, + { + "description": "Denies the lstat command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-lstat", + "markdownDescription": "Denies the lstat command without any pre-configured scope." + }, + { + "description": "Denies the mkdir command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-mkdir", + "markdownDescription": "Denies the mkdir command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the read command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read", + "markdownDescription": "Denies the read command without any pre-configured scope." + }, + { + "description": "Denies the read_dir command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-dir", + "markdownDescription": "Denies the read_dir command without any pre-configured scope." + }, + { + "description": "Denies the read_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-file", + "markdownDescription": "Denies the read_file command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-text-file", + "markdownDescription": "Denies the read_text_file command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file_lines command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-text-file-lines", + "markdownDescription": "Denies the read_text_file_lines command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file_lines_next command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-text-file-lines-next", + "markdownDescription": "Denies the read_text_file_lines_next command without any pre-configured scope." + }, + { + "description": "Denies the remove command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-remove", + "markdownDescription": "Denies the remove command without any pre-configured scope." + }, + { + "description": "Denies the rename command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-rename", + "markdownDescription": "Denies the rename command without any pre-configured scope." + }, + { + "description": "Denies the seek command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-seek", + "markdownDescription": "Denies the seek command without any pre-configured scope." + }, + { + "description": "Denies the size command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-size", + "markdownDescription": "Denies the size command without any pre-configured scope." + }, + { + "description": "Denies the stat command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-stat", + "markdownDescription": "Denies the stat command without any pre-configured scope." + }, + { + "description": "Denies the truncate command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-truncate", + "markdownDescription": "Denies the truncate command without any pre-configured scope." + }, + { + "description": "Denies the unwatch command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-unwatch", + "markdownDescription": "Denies the unwatch command without any pre-configured scope." + }, + { + "description": "Denies the watch command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-watch", + "markdownDescription": "Denies the watch command without any pre-configured scope." + }, + { + "description": "This denies read access to the\n`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", + "type": "string", + "const": "fs:deny-webview-data-linux", + "markdownDescription": "This denies read access to the\n`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered." + }, + { + "description": "This denies read access to the\n`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", + "type": "string", + "const": "fs:deny-webview-data-windows", + "markdownDescription": "This denies read access to the\n`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered." + }, + { + "description": "Denies the write command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-write", + "markdownDescription": "Denies the write command without any pre-configured scope." + }, + { + "description": "Denies the write_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-write-file", + "markdownDescription": "Denies the write_file command without any pre-configured scope." + }, + { + "description": "Denies the write_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-write-text-file", + "markdownDescription": "Denies the write_text_file command without any pre-configured scope." + }, + { + "description": "This enables all read related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-all", + "markdownDescription": "This enables all read related commands without any pre-configured accessible paths." + }, + { + "description": "This permission allows recursive read functionality on the application\nspecific base directories. \n", + "type": "string", + "const": "fs:read-app-specific-dirs-recursive", + "markdownDescription": "This permission allows recursive read functionality on the application\nspecific base directories. \n" + }, + { + "description": "This enables directory read and file metadata related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-dirs", + "markdownDescription": "This enables directory read and file metadata related commands without any pre-configured accessible paths." + }, + { + "description": "This enables file read related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-files", + "markdownDescription": "This enables file read related commands without any pre-configured accessible paths." + }, + { + "description": "This enables all index or metadata related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-meta", + "markdownDescription": "This enables all index or metadata related commands without any pre-configured accessible paths." + }, + { + "description": "An empty permission you can use to modify the global scope.\n\n## Example\n\n```json\n{\n \"identifier\": \"read-documents\",\n \"windows\": [\"main\"],\n \"permissions\": [\n \"fs:allow-read\",\n {\n \"identifier\": \"fs:scope\",\n \"allow\": [\n \"$APPDATA/documents/**/*\"\n ],\n \"deny\": [\n \"$APPDATA/documents/secret.txt\"\n ]\n }\n ]\n}\n```\n", + "type": "string", + "const": "fs:scope", + "markdownDescription": "An empty permission you can use to modify the global scope.\n\n## Example\n\n```json\n{\n \"identifier\": \"read-documents\",\n \"windows\": [\"main\"],\n \"permissions\": [\n \"fs:allow-read\",\n {\n \"identifier\": \"fs:scope\",\n \"allow\": [\n \"$APPDATA/documents/**/*\"\n ],\n \"deny\": [\n \"$APPDATA/documents/secret.txt\"\n ]\n }\n ]\n}\n```\n" + }, + { + "description": "This scope permits access to all files and list content of top level directories in the application folders.", + "type": "string", + "const": "fs:scope-app", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the application folders." + }, + { + "description": "This scope permits to list all files and folders in the application directories.", + "type": "string", + "const": "fs:scope-app-index", + "markdownDescription": "This scope permits to list all files and folders in the application directories." + }, + { + "description": "This scope permits recursive access to the complete application folders, including sub directories and files.", + "type": "string", + "const": "fs:scope-app-recursive", + "markdownDescription": "This scope permits recursive access to the complete application folders, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder.", + "type": "string", + "const": "fs:scope-appcache", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPCACHE`folder.", + "type": "string", + "const": "fs:scope-appcache-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPCACHE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-appcache-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder.", + "type": "string", + "const": "fs:scope-appconfig", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPCONFIG`folder.", + "type": "string", + "const": "fs:scope-appconfig-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPCONFIG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-appconfig-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPDATA` folder.", + "type": "string", + "const": "fs:scope-appdata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPDATA`folder.", + "type": "string", + "const": "fs:scope-appdata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPDATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-appdata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder.", + "type": "string", + "const": "fs:scope-applocaldata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPLOCALDATA`folder.", + "type": "string", + "const": "fs:scope-applocaldata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPLOCALDATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-applocaldata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPLOG` folder.", + "type": "string", + "const": "fs:scope-applog", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPLOG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPLOG`folder.", + "type": "string", + "const": "fs:scope-applog-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPLOG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-applog-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$AUDIO` folder.", + "type": "string", + "const": "fs:scope-audio", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$AUDIO` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$AUDIO`folder.", + "type": "string", + "const": "fs:scope-audio-index", + "markdownDescription": "This scope permits to list all files and folders in the `$AUDIO`folder." + }, + { + "description": "This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-audio-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$CACHE` folder.", + "type": "string", + "const": "fs:scope-cache", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$CACHE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$CACHE`folder.", + "type": "string", + "const": "fs:scope-cache-index", + "markdownDescription": "This scope permits to list all files and folders in the `$CACHE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-cache-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$CONFIG` folder.", + "type": "string", + "const": "fs:scope-config", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$CONFIG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$CONFIG`folder.", + "type": "string", + "const": "fs:scope-config-index", + "markdownDescription": "This scope permits to list all files and folders in the `$CONFIG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-config-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DATA` folder.", + "type": "string", + "const": "fs:scope-data", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DATA`folder.", + "type": "string", + "const": "fs:scope-data-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-data-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder.", + "type": "string", + "const": "fs:scope-desktop", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DESKTOP`folder.", + "type": "string", + "const": "fs:scope-desktop-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DESKTOP`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-desktop-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder.", + "type": "string", + "const": "fs:scope-document", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DOCUMENT`folder.", + "type": "string", + "const": "fs:scope-document-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DOCUMENT`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-document-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder.", + "type": "string", + "const": "fs:scope-download", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DOWNLOAD`folder.", + "type": "string", + "const": "fs:scope-download-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DOWNLOAD`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-download-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$EXE` folder.", + "type": "string", + "const": "fs:scope-exe", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$EXE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$EXE`folder.", + "type": "string", + "const": "fs:scope-exe-index", + "markdownDescription": "This scope permits to list all files and folders in the `$EXE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$EXE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-exe-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$EXE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$FONT` folder.", + "type": "string", + "const": "fs:scope-font", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$FONT` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$FONT`folder.", + "type": "string", + "const": "fs:scope-font-index", + "markdownDescription": "This scope permits to list all files and folders in the `$FONT`folder." + }, + { + "description": "This scope permits recursive access to the complete `$FONT` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-font-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$FONT` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$HOME` folder.", + "type": "string", + "const": "fs:scope-home", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$HOME` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$HOME`folder.", + "type": "string", + "const": "fs:scope-home-index", + "markdownDescription": "This scope permits to list all files and folders in the `$HOME`folder." + }, + { + "description": "This scope permits recursive access to the complete `$HOME` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-home-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$HOME` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder.", + "type": "string", + "const": "fs:scope-localdata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$LOCALDATA`folder.", + "type": "string", + "const": "fs:scope-localdata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$LOCALDATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-localdata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$LOG` folder.", + "type": "string", + "const": "fs:scope-log", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$LOG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$LOG`folder.", + "type": "string", + "const": "fs:scope-log-index", + "markdownDescription": "This scope permits to list all files and folders in the `$LOG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$LOG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-log-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$LOG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$PICTURE` folder.", + "type": "string", + "const": "fs:scope-picture", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$PICTURE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$PICTURE`folder.", + "type": "string", + "const": "fs:scope-picture-index", + "markdownDescription": "This scope permits to list all files and folders in the `$PICTURE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-picture-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder.", + "type": "string", + "const": "fs:scope-public", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$PUBLIC`folder.", + "type": "string", + "const": "fs:scope-public-index", + "markdownDescription": "This scope permits to list all files and folders in the `$PUBLIC`folder." + }, + { + "description": "This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-public-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder.", + "type": "string", + "const": "fs:scope-resource", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$RESOURCE`folder.", + "type": "string", + "const": "fs:scope-resource-index", + "markdownDescription": "This scope permits to list all files and folders in the `$RESOURCE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-resource-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder.", + "type": "string", + "const": "fs:scope-runtime", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$RUNTIME`folder.", + "type": "string", + "const": "fs:scope-runtime-index", + "markdownDescription": "This scope permits to list all files and folders in the `$RUNTIME`folder." + }, + { + "description": "This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-runtime-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$TEMP` folder.", + "type": "string", + "const": "fs:scope-temp", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$TEMP` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$TEMP`folder.", + "type": "string", + "const": "fs:scope-temp-index", + "markdownDescription": "This scope permits to list all files and folders in the `$TEMP`folder." + }, + { + "description": "This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-temp-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder.", + "type": "string", + "const": "fs:scope-template", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$TEMPLATE`folder.", + "type": "string", + "const": "fs:scope-template-index", + "markdownDescription": "This scope permits to list all files and folders in the `$TEMPLATE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-template-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$VIDEO` folder.", + "type": "string", + "const": "fs:scope-video", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$VIDEO` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$VIDEO`folder.", + "type": "string", + "const": "fs:scope-video-index", + "markdownDescription": "This scope permits to list all files and folders in the `$VIDEO`folder." + }, + { + "description": "This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-video-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files." + }, + { + "description": "This enables all write related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:write-all", + "markdownDescription": "This enables all write related commands without any pre-configured accessible paths." + }, + { + "description": "This enables all file write related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:write-files", + "markdownDescription": "This enables all file write related commands without any pre-configured accessible paths." + } + ] + } + } + }, + "then": { + "properties": { + "allow": { + "items": { + "title": "FsScopeEntry", + "description": "FS scope entry.", + "anyOf": [ + { + "description": "A path that can be accessed by the webview when using the fs APIs. FS scope path pattern.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + }, + { + "type": "object", + "required": [ + "path" + ], + "properties": { + "path": { + "description": "A path that can be accessed by the webview when using the fs APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + } + } + } + ] + } + }, + "deny": { + "items": { + "title": "FsScopeEntry", + "description": "FS scope entry.", + "anyOf": [ + { + "description": "A path that can be accessed by the webview when using the fs APIs. FS scope path pattern.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + }, + { + "type": "object", + "required": [ + "path" + ], + "properties": { + "path": { + "description": "A path that can be accessed by the webview when using the fs APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + } + } + } + ] + } + } + } + }, + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + } + } + }, + { + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + } + ], + "required": [ + "identifier" + ] + } + ] + }, + "Identifier": { + "description": "Permission identifier", + "oneOf": [ + { + "description": "Allows reading the CLI matches\n#### This default permission set includes:\n\n- `allow-cli-matches`", + "type": "string", + "const": "cli:default", + "markdownDescription": "Allows reading the CLI matches\n#### This default permission set includes:\n\n- `allow-cli-matches`" + }, + { + "description": "Enables the cli_matches command without any pre-configured scope.", + "type": "string", + "const": "cli:allow-cli-matches", + "markdownDescription": "Enables the cli_matches command without any pre-configured scope." + }, + { + "description": "Denies the cli_matches command without any pre-configured scope.", + "type": "string", + "const": "cli:deny-cli-matches", + "markdownDescription": "Denies the cli_matches command without any pre-configured scope." + }, + { + "description": "No features are enabled by default, as we believe\nthe clipboard can be inherently dangerous and it is \napplication specific if read and/or write access is needed.\n\nClipboard interaction needs to be explicitly enabled.\n", + "type": "string", + "const": "clipboard-manager:default", + "markdownDescription": "No features are enabled by default, as we believe\nthe clipboard can be inherently dangerous and it is \napplication specific if read and/or write access is needed.\n\nClipboard interaction needs to be explicitly enabled.\n" + }, + { + "description": "Enables the clear command without any pre-configured scope.", + "type": "string", + "const": "clipboard-manager:allow-clear", + "markdownDescription": "Enables the clear command without any pre-configured scope." + }, + { + "description": "Enables the read_image command without any pre-configured scope.", + "type": "string", + "const": "clipboard-manager:allow-read-image", + "markdownDescription": "Enables the read_image command without any pre-configured scope." + }, + { + "description": "Enables the read_text command without any pre-configured scope.", + "type": "string", + "const": "clipboard-manager:allow-read-text", + "markdownDescription": "Enables the read_text command without any pre-configured scope." + }, + { + "description": "Enables the write_html command without any pre-configured scope.", + "type": "string", + "const": "clipboard-manager:allow-write-html", + "markdownDescription": "Enables the write_html command without any pre-configured scope." + }, + { + "description": "Enables the write_image command without any pre-configured scope.", + "type": "string", + "const": "clipboard-manager:allow-write-image", + "markdownDescription": "Enables the write_image command without any pre-configured scope." + }, + { + "description": "Enables the write_text command without any pre-configured scope.", + "type": "string", + "const": "clipboard-manager:allow-write-text", + "markdownDescription": "Enables the write_text command without any pre-configured scope." + }, + { + "description": "Denies the clear command without any pre-configured scope.", + "type": "string", + "const": "clipboard-manager:deny-clear", + "markdownDescription": "Denies the clear command without any pre-configured scope." + }, + { + "description": "Denies the read_image command without any pre-configured scope.", + "type": "string", + "const": "clipboard-manager:deny-read-image", + "markdownDescription": "Denies the read_image command without any pre-configured scope." + }, + { + "description": "Denies the read_text command without any pre-configured scope.", + "type": "string", + "const": "clipboard-manager:deny-read-text", + "markdownDescription": "Denies the read_text command without any pre-configured scope." + }, + { + "description": "Denies the write_html command without any pre-configured scope.", + "type": "string", + "const": "clipboard-manager:deny-write-html", + "markdownDescription": "Denies the write_html command without any pre-configured scope." + }, + { + "description": "Denies the write_image command without any pre-configured scope.", + "type": "string", + "const": "clipboard-manager:deny-write-image", + "markdownDescription": "Denies the write_image command without any pre-configured scope." + }, + { + "description": "Denies the write_text command without any pre-configured scope.", + "type": "string", + "const": "clipboard-manager:deny-write-text", + "markdownDescription": "Denies the write_text command without any pre-configured scope." + }, + { + "description": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`", + "type": "string", + "const": "core:default", + "markdownDescription": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`" + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`", + "type": "string", + "const": "core:app:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`" + }, + { + "description": "Enables the app_hide command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-app-hide", + "markdownDescription": "Enables the app_hide command without any pre-configured scope." + }, + { + "description": "Enables the app_show command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-app-show", + "markdownDescription": "Enables the app_show command without any pre-configured scope." + }, + { + "description": "Enables the bundle_type command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-bundle-type", + "markdownDescription": "Enables the bundle_type command without any pre-configured scope." + }, + { + "description": "Enables the default_window_icon command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-default-window-icon", + "markdownDescription": "Enables the default_window_icon command without any pre-configured scope." + }, + { + "description": "Enables the fetch_data_store_identifiers command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-fetch-data-store-identifiers", + "markdownDescription": "Enables the fetch_data_store_identifiers command without any pre-configured scope." + }, + { + "description": "Enables the identifier command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-identifier", + "markdownDescription": "Enables the identifier command without any pre-configured scope." + }, + { + "description": "Enables the name command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-name", + "markdownDescription": "Enables the name command without any pre-configured scope." + }, + { + "description": "Enables the register_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-register-listener", + "markdownDescription": "Enables the register_listener command without any pre-configured scope." + }, + { + "description": "Enables the remove_data_store command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-remove-data-store", + "markdownDescription": "Enables the remove_data_store command without any pre-configured scope." + }, + { + "description": "Enables the remove_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-remove-listener", + "markdownDescription": "Enables the remove_listener command without any pre-configured scope." + }, + { + "description": "Enables the set_app_theme command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-set-app-theme", + "markdownDescription": "Enables the set_app_theme command without any pre-configured scope." + }, + { + "description": "Enables the set_dock_visibility command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-set-dock-visibility", + "markdownDescription": "Enables the set_dock_visibility command without any pre-configured scope." + }, + { + "description": "Enables the tauri_version command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-tauri-version", + "markdownDescription": "Enables the tauri_version command without any pre-configured scope." + }, + { + "description": "Enables the version command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-version", + "markdownDescription": "Enables the version command without any pre-configured scope." + }, + { + "description": "Denies the app_hide command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-app-hide", + "markdownDescription": "Denies the app_hide command without any pre-configured scope." + }, + { + "description": "Denies the app_show command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-app-show", + "markdownDescription": "Denies the app_show command without any pre-configured scope." + }, + { + "description": "Denies the bundle_type command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-bundle-type", + "markdownDescription": "Denies the bundle_type command without any pre-configured scope." + }, + { + "description": "Denies the default_window_icon command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-default-window-icon", + "markdownDescription": "Denies the default_window_icon command without any pre-configured scope." + }, + { + "description": "Denies the fetch_data_store_identifiers command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-fetch-data-store-identifiers", + "markdownDescription": "Denies the fetch_data_store_identifiers command without any pre-configured scope." + }, + { + "description": "Denies the identifier command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-identifier", + "markdownDescription": "Denies the identifier command without any pre-configured scope." + }, + { + "description": "Denies the name command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-name", + "markdownDescription": "Denies the name command without any pre-configured scope." + }, + { + "description": "Denies the register_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-register-listener", + "markdownDescription": "Denies the register_listener command without any pre-configured scope." + }, + { + "description": "Denies the remove_data_store command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-remove-data-store", + "markdownDescription": "Denies the remove_data_store command without any pre-configured scope." + }, + { + "description": "Denies the remove_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-remove-listener", + "markdownDescription": "Denies the remove_listener command without any pre-configured scope." + }, + { + "description": "Denies the set_app_theme command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-set-app-theme", + "markdownDescription": "Denies the set_app_theme command without any pre-configured scope." + }, + { + "description": "Denies the set_dock_visibility command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-set-dock-visibility", + "markdownDescription": "Denies the set_dock_visibility command without any pre-configured scope." + }, + { + "description": "Denies the tauri_version command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-tauri-version", + "markdownDescription": "Denies the tauri_version command without any pre-configured scope." + }, + { + "description": "Denies the version command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-version", + "markdownDescription": "Denies the version command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-listen`\n- `allow-unlisten`\n- `allow-emit`\n- `allow-emit-to`", + "type": "string", + "const": "core:event:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-listen`\n- `allow-unlisten`\n- `allow-emit`\n- `allow-emit-to`" + }, + { + "description": "Enables the emit command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-emit", + "markdownDescription": "Enables the emit command without any pre-configured scope." + }, + { + "description": "Enables the emit_to command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-emit-to", + "markdownDescription": "Enables the emit_to command without any pre-configured scope." + }, + { + "description": "Enables the listen command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-listen", + "markdownDescription": "Enables the listen command without any pre-configured scope." + }, + { + "description": "Enables the unlisten command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-unlisten", + "markdownDescription": "Enables the unlisten command without any pre-configured scope." + }, + { + "description": "Denies the emit command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-emit", + "markdownDescription": "Denies the emit command without any pre-configured scope." + }, + { + "description": "Denies the emit_to command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-emit-to", + "markdownDescription": "Denies the emit_to command without any pre-configured scope." + }, + { + "description": "Denies the listen command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-listen", + "markdownDescription": "Denies the listen command without any pre-configured scope." + }, + { + "description": "Denies the unlisten command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-unlisten", + "markdownDescription": "Denies the unlisten command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-from-bytes`\n- `allow-from-path`\n- `allow-rgba`\n- `allow-size`", + "type": "string", + "const": "core:image:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-from-bytes`\n- `allow-from-path`\n- `allow-rgba`\n- `allow-size`" + }, + { + "description": "Enables the from_bytes command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-from-bytes", + "markdownDescription": "Enables the from_bytes command without any pre-configured scope." + }, + { + "description": "Enables the from_path command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-from-path", + "markdownDescription": "Enables the from_path command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the rgba command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-rgba", + "markdownDescription": "Enables the rgba command without any pre-configured scope." + }, + { + "description": "Enables the size command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-size", + "markdownDescription": "Enables the size command without any pre-configured scope." + }, + { + "description": "Denies the from_bytes command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-from-bytes", + "markdownDescription": "Denies the from_bytes command without any pre-configured scope." + }, + { + "description": "Denies the from_path command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-from-path", + "markdownDescription": "Denies the from_path command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the rgba command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-rgba", + "markdownDescription": "Denies the rgba command without any pre-configured scope." + }, + { + "description": "Denies the size command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-size", + "markdownDescription": "Denies the size command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-append`\n- `allow-prepend`\n- `allow-insert`\n- `allow-remove`\n- `allow-remove-at`\n- `allow-items`\n- `allow-get`\n- `allow-popup`\n- `allow-create-default`\n- `allow-set-as-app-menu`\n- `allow-set-as-window-menu`\n- `allow-text`\n- `allow-set-text`\n- `allow-is-enabled`\n- `allow-set-enabled`\n- `allow-set-accelerator`\n- `allow-set-as-windows-menu-for-nsapp`\n- `allow-set-as-help-menu-for-nsapp`\n- `allow-is-checked`\n- `allow-set-checked`\n- `allow-set-icon`", + "type": "string", + "const": "core:menu:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-append`\n- `allow-prepend`\n- `allow-insert`\n- `allow-remove`\n- `allow-remove-at`\n- `allow-items`\n- `allow-get`\n- `allow-popup`\n- `allow-create-default`\n- `allow-set-as-app-menu`\n- `allow-set-as-window-menu`\n- `allow-text`\n- `allow-set-text`\n- `allow-is-enabled`\n- `allow-set-enabled`\n- `allow-set-accelerator`\n- `allow-set-as-windows-menu-for-nsapp`\n- `allow-set-as-help-menu-for-nsapp`\n- `allow-is-checked`\n- `allow-set-checked`\n- `allow-set-icon`" + }, + { + "description": "Enables the append command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-append", + "markdownDescription": "Enables the append command without any pre-configured scope." + }, + { + "description": "Enables the create_default command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-create-default", + "markdownDescription": "Enables the create_default command without any pre-configured scope." + }, + { + "description": "Enables the get command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-get", + "markdownDescription": "Enables the get command without any pre-configured scope." + }, + { + "description": "Enables the insert command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-insert", + "markdownDescription": "Enables the insert command without any pre-configured scope." + }, + { + "description": "Enables the is_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-is-checked", + "markdownDescription": "Enables the is_checked command without any pre-configured scope." + }, + { + "description": "Enables the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-is-enabled", + "markdownDescription": "Enables the is_enabled command without any pre-configured scope." + }, + { + "description": "Enables the items command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-items", + "markdownDescription": "Enables the items command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the popup command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-popup", + "markdownDescription": "Enables the popup command without any pre-configured scope." + }, + { + "description": "Enables the prepend command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-prepend", + "markdownDescription": "Enables the prepend command without any pre-configured scope." + }, + { + "description": "Enables the remove command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-remove", + "markdownDescription": "Enables the remove command without any pre-configured scope." + }, + { + "description": "Enables the remove_at command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-remove-at", + "markdownDescription": "Enables the remove_at command without any pre-configured scope." + }, + { + "description": "Enables the set_accelerator command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-accelerator", + "markdownDescription": "Enables the set_accelerator command without any pre-configured scope." + }, + { + "description": "Enables the set_as_app_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-app-menu", + "markdownDescription": "Enables the set_as_app_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-help-menu-for-nsapp", + "markdownDescription": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Enables the set_as_window_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-window-menu", + "markdownDescription": "Enables the set_as_window_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-windows-menu-for-nsapp", + "markdownDescription": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Enables the set_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-checked", + "markdownDescription": "Enables the set_checked command without any pre-configured scope." + }, + { + "description": "Enables the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-enabled", + "markdownDescription": "Enables the set_enabled command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-text", + "markdownDescription": "Enables the set_text command without any pre-configured scope." + }, + { + "description": "Enables the text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-text", + "markdownDescription": "Enables the text command without any pre-configured scope." + }, + { + "description": "Denies the append command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-append", + "markdownDescription": "Denies the append command without any pre-configured scope." + }, + { + "description": "Denies the create_default command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-create-default", + "markdownDescription": "Denies the create_default command without any pre-configured scope." + }, + { + "description": "Denies the get command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-get", + "markdownDescription": "Denies the get command without any pre-configured scope." + }, + { + "description": "Denies the insert command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-insert", + "markdownDescription": "Denies the insert command without any pre-configured scope." + }, + { + "description": "Denies the is_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-is-checked", + "markdownDescription": "Denies the is_checked command without any pre-configured scope." + }, + { + "description": "Denies the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-is-enabled", + "markdownDescription": "Denies the is_enabled command without any pre-configured scope." + }, + { + "description": "Denies the items command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-items", + "markdownDescription": "Denies the items command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the popup command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-popup", + "markdownDescription": "Denies the popup command without any pre-configured scope." + }, + { + "description": "Denies the prepend command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-prepend", + "markdownDescription": "Denies the prepend command without any pre-configured scope." + }, + { + "description": "Denies the remove command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-remove", + "markdownDescription": "Denies the remove command without any pre-configured scope." + }, + { + "description": "Denies the remove_at command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-remove-at", + "markdownDescription": "Denies the remove_at command without any pre-configured scope." + }, + { + "description": "Denies the set_accelerator command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-accelerator", + "markdownDescription": "Denies the set_accelerator command without any pre-configured scope." + }, + { + "description": "Denies the set_as_app_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-app-menu", + "markdownDescription": "Denies the set_as_app_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-help-menu-for-nsapp", + "markdownDescription": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Denies the set_as_window_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-window-menu", + "markdownDescription": "Denies the set_as_window_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-windows-menu-for-nsapp", + "markdownDescription": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Denies the set_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-checked", + "markdownDescription": "Denies the set_checked command without any pre-configured scope." + }, + { + "description": "Denies the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-enabled", + "markdownDescription": "Denies the set_enabled command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-text", + "markdownDescription": "Denies the set_text command without any pre-configured scope." + }, + { + "description": "Denies the text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-text", + "markdownDescription": "Denies the text command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-resolve-directory`\n- `allow-resolve`\n- `allow-normalize`\n- `allow-join`\n- `allow-dirname`\n- `allow-extname`\n- `allow-basename`\n- `allow-is-absolute`", + "type": "string", + "const": "core:path:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-resolve-directory`\n- `allow-resolve`\n- `allow-normalize`\n- `allow-join`\n- `allow-dirname`\n- `allow-extname`\n- `allow-basename`\n- `allow-is-absolute`" + }, + { + "description": "Enables the basename command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-basename", + "markdownDescription": "Enables the basename command without any pre-configured scope." + }, + { + "description": "Enables the dirname command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-dirname", + "markdownDescription": "Enables the dirname command without any pre-configured scope." + }, + { + "description": "Enables the extname command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-extname", + "markdownDescription": "Enables the extname command without any pre-configured scope." + }, + { + "description": "Enables the is_absolute command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-is-absolute", + "markdownDescription": "Enables the is_absolute command without any pre-configured scope." + }, + { + "description": "Enables the join command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-join", + "markdownDescription": "Enables the join command without any pre-configured scope." + }, + { + "description": "Enables the normalize command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-normalize", + "markdownDescription": "Enables the normalize command without any pre-configured scope." + }, + { + "description": "Enables the resolve command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-resolve", + "markdownDescription": "Enables the resolve command without any pre-configured scope." + }, + { + "description": "Enables the resolve_directory command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-resolve-directory", + "markdownDescription": "Enables the resolve_directory command without any pre-configured scope." + }, + { + "description": "Denies the basename command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-basename", + "markdownDescription": "Denies the basename command without any pre-configured scope." + }, + { + "description": "Denies the dirname command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-dirname", + "markdownDescription": "Denies the dirname command without any pre-configured scope." + }, + { + "description": "Denies the extname command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-extname", + "markdownDescription": "Denies the extname command without any pre-configured scope." + }, + { + "description": "Denies the is_absolute command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-is-absolute", + "markdownDescription": "Denies the is_absolute command without any pre-configured scope." + }, + { + "description": "Denies the join command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-join", + "markdownDescription": "Denies the join command without any pre-configured scope." + }, + { + "description": "Denies the normalize command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-normalize", + "markdownDescription": "Denies the normalize command without any pre-configured scope." + }, + { + "description": "Denies the resolve command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-resolve", + "markdownDescription": "Denies the resolve command without any pre-configured scope." + }, + { + "description": "Denies the resolve_directory command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-resolve-directory", + "markdownDescription": "Denies the resolve_directory command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-close`", + "type": "string", + "const": "core:resources:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-close`" + }, + { + "description": "Enables the close command without any pre-configured scope.", + "type": "string", + "const": "core:resources:allow-close", + "markdownDescription": "Enables the close command without any pre-configured scope." + }, + { + "description": "Denies the close command without any pre-configured scope.", + "type": "string", + "const": "core:resources:deny-close", + "markdownDescription": "Denies the close command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-show-menu-on-left-click`", + "type": "string", + "const": "core:tray:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-show-menu-on-left-click`" + }, + { + "description": "Enables the get_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-get-by-id", + "markdownDescription": "Enables the get_by_id command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the remove_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-remove-by-id", + "markdownDescription": "Enables the remove_by_id command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_icon_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon-as-template", + "markdownDescription": "Enables the set_icon_as_template command without any pre-configured scope." + }, + { + "description": "Enables the set_menu command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-menu", + "markdownDescription": "Enables the set_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_show_menu_on_left_click command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-show-menu-on-left-click", + "markdownDescription": "Enables the set_show_menu_on_left_click command without any pre-configured scope." + }, + { + "description": "Enables the set_temp_dir_path command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-temp-dir-path", + "markdownDescription": "Enables the set_temp_dir_path command without any pre-configured scope." + }, + { + "description": "Enables the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-title", + "markdownDescription": "Enables the set_title command without any pre-configured scope." + }, + { + "description": "Enables the set_tooltip command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-tooltip", + "markdownDescription": "Enables the set_tooltip command without any pre-configured scope." + }, + { + "description": "Enables the set_visible command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-visible", + "markdownDescription": "Enables the set_visible command without any pre-configured scope." + }, + { + "description": "Denies the get_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-get-by-id", + "markdownDescription": "Denies the get_by_id command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the remove_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-remove-by-id", + "markdownDescription": "Denies the remove_by_id command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_icon_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon-as-template", + "markdownDescription": "Denies the set_icon_as_template command without any pre-configured scope." + }, + { + "description": "Denies the set_menu command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-menu", + "markdownDescription": "Denies the set_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_show_menu_on_left_click command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-show-menu-on-left-click", + "markdownDescription": "Denies the set_show_menu_on_left_click command without any pre-configured scope." + }, + { + "description": "Denies the set_temp_dir_path command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-temp-dir-path", + "markdownDescription": "Denies the set_temp_dir_path command without any pre-configured scope." + }, + { + "description": "Denies the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-title", + "markdownDescription": "Denies the set_title command without any pre-configured scope." + }, + { + "description": "Denies the set_tooltip command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-tooltip", + "markdownDescription": "Denies the set_tooltip command without any pre-configured scope." + }, + { + "description": "Denies the set_visible command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-visible", + "markdownDescription": "Denies the set_visible command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-webviews`\n- `allow-webview-position`\n- `allow-webview-size`\n- `allow-internal-toggle-devtools`", + "type": "string", + "const": "core:webview:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-webviews`\n- `allow-webview-position`\n- `allow-webview-size`\n- `allow-internal-toggle-devtools`" + }, + { + "description": "Enables the clear_all_browsing_data command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-clear-all-browsing-data", + "markdownDescription": "Enables the clear_all_browsing_data command without any pre-configured scope." + }, + { + "description": "Enables the create_webview command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-create-webview", + "markdownDescription": "Enables the create_webview command without any pre-configured scope." + }, + { + "description": "Enables the create_webview_window command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-create-webview-window", + "markdownDescription": "Enables the create_webview_window command without any pre-configured scope." + }, + { + "description": "Enables the get_all_webviews command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-get-all-webviews", + "markdownDescription": "Enables the get_all_webviews command without any pre-configured scope." + }, + { + "description": "Enables the internal_toggle_devtools command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-internal-toggle-devtools", + "markdownDescription": "Enables the internal_toggle_devtools command without any pre-configured scope." + }, + { + "description": "Enables the print command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-print", + "markdownDescription": "Enables the print command without any pre-configured scope." + }, + { + "description": "Enables the reparent command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-reparent", + "markdownDescription": "Enables the reparent command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_auto_resize command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-auto-resize", + "markdownDescription": "Enables the set_webview_auto_resize command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-background-color", + "markdownDescription": "Enables the set_webview_background_color command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_focus command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-focus", + "markdownDescription": "Enables the set_webview_focus command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-position", + "markdownDescription": "Enables the set_webview_position command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-size", + "markdownDescription": "Enables the set_webview_size command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_zoom command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-zoom", + "markdownDescription": "Enables the set_webview_zoom command without any pre-configured scope." + }, + { + "description": "Enables the webview_close command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-close", + "markdownDescription": "Enables the webview_close command without any pre-configured scope." + }, + { + "description": "Enables the webview_hide command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-hide", + "markdownDescription": "Enables the webview_hide command without any pre-configured scope." + }, + { + "description": "Enables the webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-position", + "markdownDescription": "Enables the webview_position command without any pre-configured scope." + }, + { + "description": "Enables the webview_show command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-show", + "markdownDescription": "Enables the webview_show command without any pre-configured scope." + }, + { + "description": "Enables the webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-size", + "markdownDescription": "Enables the webview_size command without any pre-configured scope." + }, + { + "description": "Denies the clear_all_browsing_data command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-clear-all-browsing-data", + "markdownDescription": "Denies the clear_all_browsing_data command without any pre-configured scope." + }, + { + "description": "Denies the create_webview command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-create-webview", + "markdownDescription": "Denies the create_webview command without any pre-configured scope." + }, + { + "description": "Denies the create_webview_window command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-create-webview-window", + "markdownDescription": "Denies the create_webview_window command without any pre-configured scope." + }, + { + "description": "Denies the get_all_webviews command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-get-all-webviews", + "markdownDescription": "Denies the get_all_webviews command without any pre-configured scope." + }, + { + "description": "Denies the internal_toggle_devtools command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-internal-toggle-devtools", + "markdownDescription": "Denies the internal_toggle_devtools command without any pre-configured scope." + }, + { + "description": "Denies the print command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-print", + "markdownDescription": "Denies the print command without any pre-configured scope." + }, + { + "description": "Denies the reparent command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-reparent", + "markdownDescription": "Denies the reparent command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_auto_resize command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-auto-resize", + "markdownDescription": "Denies the set_webview_auto_resize command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-background-color", + "markdownDescription": "Denies the set_webview_background_color command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_focus command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-focus", + "markdownDescription": "Denies the set_webview_focus command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-position", + "markdownDescription": "Denies the set_webview_position command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-size", + "markdownDescription": "Denies the set_webview_size command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_zoom command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-zoom", + "markdownDescription": "Denies the set_webview_zoom command without any pre-configured scope." + }, + { + "description": "Denies the webview_close command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-close", + "markdownDescription": "Denies the webview_close command without any pre-configured scope." + }, + { + "description": "Denies the webview_hide command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-hide", + "markdownDescription": "Denies the webview_hide command without any pre-configured scope." + }, + { + "description": "Denies the webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-position", + "markdownDescription": "Denies the webview_position command without any pre-configured scope." + }, + { + "description": "Denies the webview_show command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-show", + "markdownDescription": "Denies the webview_show command without any pre-configured scope." + }, + { + "description": "Denies the webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-size", + "markdownDescription": "Denies the webview_size command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-internal-toggle-maximize`", + "type": "string", + "const": "core:window:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-internal-toggle-maximize`" + }, + { + "description": "Enables the available_monitors command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-available-monitors", + "markdownDescription": "Enables the available_monitors command without any pre-configured scope." + }, + { + "description": "Enables the center command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-center", + "markdownDescription": "Enables the center command without any pre-configured scope." + }, + { + "description": "Enables the close command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-close", + "markdownDescription": "Enables the close command without any pre-configured scope." + }, + { + "description": "Enables the create command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-create", + "markdownDescription": "Enables the create command without any pre-configured scope." + }, + { + "description": "Enables the current_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-current-monitor", + "markdownDescription": "Enables the current_monitor command without any pre-configured scope." + }, + { + "description": "Enables the cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-cursor-position", + "markdownDescription": "Enables the cursor_position command without any pre-configured scope." + }, + { + "description": "Enables the destroy command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-destroy", + "markdownDescription": "Enables the destroy command without any pre-configured scope." + }, + { + "description": "Enables the get_all_windows command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-get-all-windows", + "markdownDescription": "Enables the get_all_windows command without any pre-configured scope." + }, + { + "description": "Enables the hide command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-hide", + "markdownDescription": "Enables the hide command without any pre-configured scope." + }, + { + "description": "Enables the inner_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-inner-position", + "markdownDescription": "Enables the inner_position command without any pre-configured scope." + }, + { + "description": "Enables the inner_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-inner-size", + "markdownDescription": "Enables the inner_size command without any pre-configured scope." + }, + { + "description": "Enables the internal_toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-internal-toggle-maximize", + "markdownDescription": "Enables the internal_toggle_maximize command without any pre-configured scope." + }, + { + "description": "Enables the is_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-always-on-top", + "markdownDescription": "Enables the is_always_on_top command without any pre-configured scope." + }, + { + "description": "Enables the is_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-closable", + "markdownDescription": "Enables the is_closable command without any pre-configured scope." + }, + { + "description": "Enables the is_decorated command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-decorated", + "markdownDescription": "Enables the is_decorated command without any pre-configured scope." + }, + { + "description": "Enables the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-enabled", + "markdownDescription": "Enables the is_enabled command without any pre-configured scope." + }, + { + "description": "Enables the is_focused command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-focused", + "markdownDescription": "Enables the is_focused command without any pre-configured scope." + }, + { + "description": "Enables the is_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-fullscreen", + "markdownDescription": "Enables the is_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the is_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-maximizable", + "markdownDescription": "Enables the is_maximizable command without any pre-configured scope." + }, + { + "description": "Enables the is_maximized command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-maximized", + "markdownDescription": "Enables the is_maximized command without any pre-configured scope." + }, + { + "description": "Enables the is_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-minimizable", + "markdownDescription": "Enables the is_minimizable command without any pre-configured scope." + }, + { + "description": "Enables the is_minimized command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-minimized", + "markdownDescription": "Enables the is_minimized command without any pre-configured scope." + }, + { + "description": "Enables the is_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-resizable", + "markdownDescription": "Enables the is_resizable command without any pre-configured scope." + }, + { + "description": "Enables the is_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-visible", + "markdownDescription": "Enables the is_visible command without any pre-configured scope." + }, + { + "description": "Enables the maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-maximize", + "markdownDescription": "Enables the maximize command without any pre-configured scope." + }, + { + "description": "Enables the minimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-minimize", + "markdownDescription": "Enables the minimize command without any pre-configured scope." + }, + { + "description": "Enables the monitor_from_point command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-monitor-from-point", + "markdownDescription": "Enables the monitor_from_point command without any pre-configured scope." + }, + { + "description": "Enables the outer_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-outer-position", + "markdownDescription": "Enables the outer_position command without any pre-configured scope." + }, + { + "description": "Enables the outer_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-outer-size", + "markdownDescription": "Enables the outer_size command without any pre-configured scope." + }, + { + "description": "Enables the primary_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-primary-monitor", + "markdownDescription": "Enables the primary_monitor command without any pre-configured scope." + }, + { + "description": "Enables the request_user_attention command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-request-user-attention", + "markdownDescription": "Enables the request_user_attention command without any pre-configured scope." + }, + { + "description": "Enables the scale_factor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-scale-factor", + "markdownDescription": "Enables the scale_factor command without any pre-configured scope." + }, + { + "description": "Enables the set_always_on_bottom command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-always-on-bottom", + "markdownDescription": "Enables the set_always_on_bottom command without any pre-configured scope." + }, + { + "description": "Enables the set_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-always-on-top", + "markdownDescription": "Enables the set_always_on_top command without any pre-configured scope." + }, + { + "description": "Enables the set_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-background-color", + "markdownDescription": "Enables the set_background_color command without any pre-configured scope." + }, + { + "description": "Enables the set_badge_count command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-badge-count", + "markdownDescription": "Enables the set_badge_count command without any pre-configured scope." + }, + { + "description": "Enables the set_badge_label command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-badge-label", + "markdownDescription": "Enables the set_badge_label command without any pre-configured scope." + }, + { + "description": "Enables the set_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-closable", + "markdownDescription": "Enables the set_closable command without any pre-configured scope." + }, + { + "description": "Enables the set_content_protected command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-content-protected", + "markdownDescription": "Enables the set_content_protected command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_grab command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-grab", + "markdownDescription": "Enables the set_cursor_grab command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-icon", + "markdownDescription": "Enables the set_cursor_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-position", + "markdownDescription": "Enables the set_cursor_position command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-visible", + "markdownDescription": "Enables the set_cursor_visible command without any pre-configured scope." + }, + { + "description": "Enables the set_decorations command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-decorations", + "markdownDescription": "Enables the set_decorations command without any pre-configured scope." + }, + { + "description": "Enables the set_effects command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-effects", + "markdownDescription": "Enables the set_effects command without any pre-configured scope." + }, + { + "description": "Enables the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-enabled", + "markdownDescription": "Enables the set_enabled command without any pre-configured scope." + }, + { + "description": "Enables the set_focus command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-focus", + "markdownDescription": "Enables the set_focus command without any pre-configured scope." + }, + { + "description": "Enables the set_focusable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-focusable", + "markdownDescription": "Enables the set_focusable command without any pre-configured scope." + }, + { + "description": "Enables the set_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-fullscreen", + "markdownDescription": "Enables the set_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_ignore_cursor_events command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-ignore-cursor-events", + "markdownDescription": "Enables the set_ignore_cursor_events command without any pre-configured scope." + }, + { + "description": "Enables the set_max_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-max-size", + "markdownDescription": "Enables the set_max_size command without any pre-configured scope." + }, + { + "description": "Enables the set_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-maximizable", + "markdownDescription": "Enables the set_maximizable command without any pre-configured scope." + }, + { + "description": "Enables the set_min_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-min-size", + "markdownDescription": "Enables the set_min_size command without any pre-configured scope." + }, + { + "description": "Enables the set_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-minimizable", + "markdownDescription": "Enables the set_minimizable command without any pre-configured scope." + }, + { + "description": "Enables the set_overlay_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-overlay-icon", + "markdownDescription": "Enables the set_overlay_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-position", + "markdownDescription": "Enables the set_position command without any pre-configured scope." + }, + { + "description": "Enables the set_progress_bar command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-progress-bar", + "markdownDescription": "Enables the set_progress_bar command without any pre-configured scope." + }, + { + "description": "Enables the set_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-resizable", + "markdownDescription": "Enables the set_resizable command without any pre-configured scope." + }, + { + "description": "Enables the set_shadow command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-shadow", + "markdownDescription": "Enables the set_shadow command without any pre-configured scope." + }, + { + "description": "Enables the set_simple_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-simple-fullscreen", + "markdownDescription": "Enables the set_simple_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the set_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-size", + "markdownDescription": "Enables the set_size command without any pre-configured scope." + }, + { + "description": "Enables the set_size_constraints command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-size-constraints", + "markdownDescription": "Enables the set_size_constraints command without any pre-configured scope." + }, + { + "description": "Enables the set_skip_taskbar command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-skip-taskbar", + "markdownDescription": "Enables the set_skip_taskbar command without any pre-configured scope." + }, + { + "description": "Enables the set_theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-theme", + "markdownDescription": "Enables the set_theme command without any pre-configured scope." + }, + { + "description": "Enables the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-title", + "markdownDescription": "Enables the set_title command without any pre-configured scope." + }, + { + "description": "Enables the set_title_bar_style command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-title-bar-style", + "markdownDescription": "Enables the set_title_bar_style command without any pre-configured scope." + }, + { + "description": "Enables the set_visible_on_all_workspaces command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-visible-on-all-workspaces", + "markdownDescription": "Enables the set_visible_on_all_workspaces command without any pre-configured scope." + }, + { + "description": "Enables the show command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-show", + "markdownDescription": "Enables the show command without any pre-configured scope." + }, + { + "description": "Enables the start_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-start-dragging", + "markdownDescription": "Enables the start_dragging command without any pre-configured scope." + }, + { + "description": "Enables the start_resize_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-start-resize-dragging", + "markdownDescription": "Enables the start_resize_dragging command without any pre-configured scope." + }, + { + "description": "Enables the theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-theme", + "markdownDescription": "Enables the theme command without any pre-configured scope." + }, + { + "description": "Enables the title command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-title", + "markdownDescription": "Enables the title command without any pre-configured scope." + }, + { + "description": "Enables the toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-toggle-maximize", + "markdownDescription": "Enables the toggle_maximize command without any pre-configured scope." + }, + { + "description": "Enables the unmaximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-unmaximize", + "markdownDescription": "Enables the unmaximize command without any pre-configured scope." + }, + { + "description": "Enables the unminimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-unminimize", + "markdownDescription": "Enables the unminimize command without any pre-configured scope." + }, + { + "description": "Denies the available_monitors command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-available-monitors", + "markdownDescription": "Denies the available_monitors command without any pre-configured scope." + }, + { + "description": "Denies the center command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-center", + "markdownDescription": "Denies the center command without any pre-configured scope." + }, + { + "description": "Denies the close command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-close", + "markdownDescription": "Denies the close command without any pre-configured scope." + }, + { + "description": "Denies the create command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-create", + "markdownDescription": "Denies the create command without any pre-configured scope." + }, + { + "description": "Denies the current_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-current-monitor", + "markdownDescription": "Denies the current_monitor command without any pre-configured scope." + }, + { + "description": "Denies the cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-cursor-position", + "markdownDescription": "Denies the cursor_position command without any pre-configured scope." + }, + { + "description": "Denies the destroy command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-destroy", + "markdownDescription": "Denies the destroy command without any pre-configured scope." + }, + { + "description": "Denies the get_all_windows command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-get-all-windows", + "markdownDescription": "Denies the get_all_windows command without any pre-configured scope." + }, + { + "description": "Denies the hide command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-hide", + "markdownDescription": "Denies the hide command without any pre-configured scope." + }, + { + "description": "Denies the inner_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-inner-position", + "markdownDescription": "Denies the inner_position command without any pre-configured scope." + }, + { + "description": "Denies the inner_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-inner-size", + "markdownDescription": "Denies the inner_size command without any pre-configured scope." + }, + { + "description": "Denies the internal_toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-internal-toggle-maximize", + "markdownDescription": "Denies the internal_toggle_maximize command without any pre-configured scope." + }, + { + "description": "Denies the is_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-always-on-top", + "markdownDescription": "Denies the is_always_on_top command without any pre-configured scope." + }, + { + "description": "Denies the is_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-closable", + "markdownDescription": "Denies the is_closable command without any pre-configured scope." + }, + { + "description": "Denies the is_decorated command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-decorated", + "markdownDescription": "Denies the is_decorated command without any pre-configured scope." + }, + { + "description": "Denies the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-enabled", + "markdownDescription": "Denies the is_enabled command without any pre-configured scope." + }, + { + "description": "Denies the is_focused command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-focused", + "markdownDescription": "Denies the is_focused command without any pre-configured scope." + }, + { + "description": "Denies the is_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-fullscreen", + "markdownDescription": "Denies the is_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the is_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-maximizable", + "markdownDescription": "Denies the is_maximizable command without any pre-configured scope." + }, + { + "description": "Denies the is_maximized command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-maximized", + "markdownDescription": "Denies the is_maximized command without any pre-configured scope." + }, + { + "description": "Denies the is_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-minimizable", + "markdownDescription": "Denies the is_minimizable command without any pre-configured scope." + }, + { + "description": "Denies the is_minimized command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-minimized", + "markdownDescription": "Denies the is_minimized command without any pre-configured scope." + }, + { + "description": "Denies the is_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-resizable", + "markdownDescription": "Denies the is_resizable command without any pre-configured scope." + }, + { + "description": "Denies the is_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-visible", + "markdownDescription": "Denies the is_visible command without any pre-configured scope." + }, + { + "description": "Denies the maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-maximize", + "markdownDescription": "Denies the maximize command without any pre-configured scope." + }, + { + "description": "Denies the minimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-minimize", + "markdownDescription": "Denies the minimize command without any pre-configured scope." + }, + { + "description": "Denies the monitor_from_point command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-monitor-from-point", + "markdownDescription": "Denies the monitor_from_point command without any pre-configured scope." + }, + { + "description": "Denies the outer_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-outer-position", + "markdownDescription": "Denies the outer_position command without any pre-configured scope." + }, + { + "description": "Denies the outer_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-outer-size", + "markdownDescription": "Denies the outer_size command without any pre-configured scope." + }, + { + "description": "Denies the primary_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-primary-monitor", + "markdownDescription": "Denies the primary_monitor command without any pre-configured scope." + }, + { + "description": "Denies the request_user_attention command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-request-user-attention", + "markdownDescription": "Denies the request_user_attention command without any pre-configured scope." + }, + { + "description": "Denies the scale_factor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-scale-factor", + "markdownDescription": "Denies the scale_factor command without any pre-configured scope." + }, + { + "description": "Denies the set_always_on_bottom command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-bottom", + "markdownDescription": "Denies the set_always_on_bottom command without any pre-configured scope." + }, + { + "description": "Denies the set_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-top", + "markdownDescription": "Denies the set_always_on_top command without any pre-configured scope." + }, + { + "description": "Denies the set_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-background-color", + "markdownDescription": "Denies the set_background_color command without any pre-configured scope." + }, + { + "description": "Denies the set_badge_count command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-badge-count", + "markdownDescription": "Denies the set_badge_count command without any pre-configured scope." + }, + { + "description": "Denies the set_badge_label command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-badge-label", + "markdownDescription": "Denies the set_badge_label command without any pre-configured scope." + }, + { + "description": "Denies the set_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-closable", + "markdownDescription": "Denies the set_closable command without any pre-configured scope." + }, + { + "description": "Denies the set_content_protected command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-content-protected", + "markdownDescription": "Denies the set_content_protected command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_grab command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-grab", + "markdownDescription": "Denies the set_cursor_grab command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-icon", + "markdownDescription": "Denies the set_cursor_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-position", + "markdownDescription": "Denies the set_cursor_position command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-visible", + "markdownDescription": "Denies the set_cursor_visible command without any pre-configured scope." + }, + { + "description": "Denies the set_decorations command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-decorations", + "markdownDescription": "Denies the set_decorations command without any pre-configured scope." + }, + { + "description": "Denies the set_effects command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-effects", + "markdownDescription": "Denies the set_effects command without any pre-configured scope." + }, + { + "description": "Denies the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-enabled", + "markdownDescription": "Denies the set_enabled command without any pre-configured scope." + }, + { + "description": "Denies the set_focus command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-focus", + "markdownDescription": "Denies the set_focus command without any pre-configured scope." + }, + { + "description": "Denies the set_focusable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-focusable", + "markdownDescription": "Denies the set_focusable command without any pre-configured scope." + }, + { + "description": "Denies the set_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-fullscreen", + "markdownDescription": "Denies the set_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_ignore_cursor_events command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-ignore-cursor-events", + "markdownDescription": "Denies the set_ignore_cursor_events command without any pre-configured scope." + }, + { + "description": "Denies the set_max_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-max-size", + "markdownDescription": "Denies the set_max_size command without any pre-configured scope." + }, + { + "description": "Denies the set_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-maximizable", + "markdownDescription": "Denies the set_maximizable command without any pre-configured scope." + }, + { + "description": "Denies the set_min_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-min-size", + "markdownDescription": "Denies the set_min_size command without any pre-configured scope." + }, + { + "description": "Denies the set_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-minimizable", + "markdownDescription": "Denies the set_minimizable command without any pre-configured scope." + }, + { + "description": "Denies the set_overlay_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-overlay-icon", + "markdownDescription": "Denies the set_overlay_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-position", + "markdownDescription": "Denies the set_position command without any pre-configured scope." + }, + { + "description": "Denies the set_progress_bar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-progress-bar", + "markdownDescription": "Denies the set_progress_bar command without any pre-configured scope." + }, + { + "description": "Denies the set_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-resizable", + "markdownDescription": "Denies the set_resizable command without any pre-configured scope." + }, + { + "description": "Denies the set_shadow command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-shadow", + "markdownDescription": "Denies the set_shadow command without any pre-configured scope." + }, + { + "description": "Denies the set_simple_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-simple-fullscreen", + "markdownDescription": "Denies the set_simple_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the set_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size", + "markdownDescription": "Denies the set_size command without any pre-configured scope." + }, + { + "description": "Denies the set_size_constraints command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size-constraints", + "markdownDescription": "Denies the set_size_constraints command without any pre-configured scope." + }, + { + "description": "Denies the set_skip_taskbar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-skip-taskbar", + "markdownDescription": "Denies the set_skip_taskbar command without any pre-configured scope." + }, + { + "description": "Denies the set_theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-theme", + "markdownDescription": "Denies the set_theme command without any pre-configured scope." + }, + { + "description": "Denies the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title", + "markdownDescription": "Denies the set_title command without any pre-configured scope." + }, + { + "description": "Denies the set_title_bar_style command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title-bar-style", + "markdownDescription": "Denies the set_title_bar_style command without any pre-configured scope." + }, + { + "description": "Denies the set_visible_on_all_workspaces command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-visible-on-all-workspaces", + "markdownDescription": "Denies the set_visible_on_all_workspaces command without any pre-configured scope." + }, + { + "description": "Denies the show command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-show", + "markdownDescription": "Denies the show command without any pre-configured scope." + }, + { + "description": "Denies the start_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-dragging", + "markdownDescription": "Denies the start_dragging command without any pre-configured scope." + }, + { + "description": "Denies the start_resize_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-resize-dragging", + "markdownDescription": "Denies the start_resize_dragging command without any pre-configured scope." + }, + { + "description": "Denies the theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-theme", + "markdownDescription": "Denies the theme command without any pre-configured scope." + }, + { + "description": "Denies the title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-title", + "markdownDescription": "Denies the title command without any pre-configured scope." + }, + { + "description": "Denies the toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-toggle-maximize", + "markdownDescription": "Denies the toggle_maximize command without any pre-configured scope." + }, + { + "description": "Denies the unmaximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-unmaximize", + "markdownDescription": "Denies the unmaximize command without any pre-configured scope." + }, + { + "description": "Denies the unminimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-unminimize", + "markdownDescription": "Denies the unminimize command without any pre-configured scope." + }, + { + "description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`", + "type": "string", + "const": "dialog:default", + "markdownDescription": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`" + }, + { + "description": "Enables the ask command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-ask", + "markdownDescription": "Enables the ask command without any pre-configured scope." + }, + { + "description": "Enables the confirm command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-confirm", + "markdownDescription": "Enables the confirm command without any pre-configured scope." + }, + { + "description": "Enables the message command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-message", + "markdownDescription": "Enables the message command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Enables the save command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-save", + "markdownDescription": "Enables the save command without any pre-configured scope." + }, + { + "description": "Denies the ask command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-ask", + "markdownDescription": "Denies the ask command without any pre-configured scope." + }, + { + "description": "Denies the confirm command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-confirm", + "markdownDescription": "Denies the confirm command without any pre-configured scope." + }, + { + "description": "Denies the message command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-message", + "markdownDescription": "Denies the message command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the save command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-save", + "markdownDescription": "Denies the save command without any pre-configured scope." + }, + { + "description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### This default permission set includes:\n\n- `create-app-specific-dirs`\n- `read-app-specific-dirs-recursive`\n- `deny-default`", + "type": "string", + "const": "fs:default", + "markdownDescription": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### This default permission set includes:\n\n- `create-app-specific-dirs`\n- `read-app-specific-dirs-recursive`\n- `deny-default`" + }, + { + "description": "This allows non-recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-index`", + "type": "string", + "const": "fs:allow-app-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-index`" + }, + { + "description": "This allows full recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-recursive`", + "type": "string", + "const": "fs:allow-app-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive read access to the application folders.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app`", + "type": "string", + "const": "fs:allow-app-read", + "markdownDescription": "This allows non-recursive read access to the application folders.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app`" + }, + { + "description": "This allows full recursive read access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app-recursive`", + "type": "string", + "const": "fs:allow-app-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive write access to the application folders.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app`", + "type": "string", + "const": "fs:allow-app-write", + "markdownDescription": "This allows non-recursive write access to the application folders.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app`" + }, + { + "description": "This allows full recursive write access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app-recursive`", + "type": "string", + "const": "fs:allow-app-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-index`", + "type": "string", + "const": "fs:allow-appcache-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-recursive`", + "type": "string", + "const": "fs:allow-appcache-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache`", + "type": "string", + "const": "fs:allow-appcache-read", + "markdownDescription": "This allows non-recursive read access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache`" + }, + { + "description": "This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache-recursive`", + "type": "string", + "const": "fs:allow-appcache-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache`", + "type": "string", + "const": "fs:allow-appcache-write", + "markdownDescription": "This allows non-recursive write access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache`" + }, + { + "description": "This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache-recursive`", + "type": "string", + "const": "fs:allow-appcache-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-index`", + "type": "string", + "const": "fs:allow-appconfig-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "fs:allow-appconfig-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig`", + "type": "string", + "const": "fs:allow-appconfig-read", + "markdownDescription": "This allows non-recursive read access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig`" + }, + { + "description": "This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "fs:allow-appconfig-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig`", + "type": "string", + "const": "fs:allow-appconfig-write", + "markdownDescription": "This allows non-recursive write access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig`" + }, + { + "description": "This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "fs:allow-appconfig-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-index`", + "type": "string", + "const": "fs:allow-appdata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-recursive`", + "type": "string", + "const": "fs:allow-appdata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata`", + "type": "string", + "const": "fs:allow-appdata-read", + "markdownDescription": "This allows non-recursive read access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata`" + }, + { + "description": "This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata-recursive`", + "type": "string", + "const": "fs:allow-appdata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata`", + "type": "string", + "const": "fs:allow-appdata-write", + "markdownDescription": "This allows non-recursive write access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata`" + }, + { + "description": "This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata-recursive`", + "type": "string", + "const": "fs:allow-appdata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-index`", + "type": "string", + "const": "fs:allow-applocaldata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "fs:allow-applocaldata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata`", + "type": "string", + "const": "fs:allow-applocaldata-read", + "markdownDescription": "This allows non-recursive read access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata`" + }, + { + "description": "This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "fs:allow-applocaldata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata`", + "type": "string", + "const": "fs:allow-applocaldata-write", + "markdownDescription": "This allows non-recursive write access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata`" + }, + { + "description": "This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "fs:allow-applocaldata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-index`", + "type": "string", + "const": "fs:allow-applog-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-recursive`", + "type": "string", + "const": "fs:allow-applog-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog`", + "type": "string", + "const": "fs:allow-applog-read", + "markdownDescription": "This allows non-recursive read access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog`" + }, + { + "description": "This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog-recursive`", + "type": "string", + "const": "fs:allow-applog-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog`", + "type": "string", + "const": "fs:allow-applog-write", + "markdownDescription": "This allows non-recursive write access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog`" + }, + { + "description": "This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog-recursive`", + "type": "string", + "const": "fs:allow-applog-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-index`", + "type": "string", + "const": "fs:allow-audio-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-recursive`", + "type": "string", + "const": "fs:allow-audio-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio`", + "type": "string", + "const": "fs:allow-audio-read", + "markdownDescription": "This allows non-recursive read access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio`" + }, + { + "description": "This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio-recursive`", + "type": "string", + "const": "fs:allow-audio-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio`", + "type": "string", + "const": "fs:allow-audio-write", + "markdownDescription": "This allows non-recursive write access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio`" + }, + { + "description": "This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio-recursive`", + "type": "string", + "const": "fs:allow-audio-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-index`", + "type": "string", + "const": "fs:allow-cache-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-recursive`", + "type": "string", + "const": "fs:allow-cache-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache`", + "type": "string", + "const": "fs:allow-cache-read", + "markdownDescription": "This allows non-recursive read access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache`" + }, + { + "description": "This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache-recursive`", + "type": "string", + "const": "fs:allow-cache-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache`", + "type": "string", + "const": "fs:allow-cache-write", + "markdownDescription": "This allows non-recursive write access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache`" + }, + { + "description": "This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache-recursive`", + "type": "string", + "const": "fs:allow-cache-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-index`", + "type": "string", + "const": "fs:allow-config-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-recursive`", + "type": "string", + "const": "fs:allow-config-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config`", + "type": "string", + "const": "fs:allow-config-read", + "markdownDescription": "This allows non-recursive read access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config`" + }, + { + "description": "This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config-recursive`", + "type": "string", + "const": "fs:allow-config-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config`", + "type": "string", + "const": "fs:allow-config-write", + "markdownDescription": "This allows non-recursive write access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config`" + }, + { + "description": "This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config-recursive`", + "type": "string", + "const": "fs:allow-config-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-index`", + "type": "string", + "const": "fs:allow-data-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-recursive`", + "type": "string", + "const": "fs:allow-data-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data`", + "type": "string", + "const": "fs:allow-data-read", + "markdownDescription": "This allows non-recursive read access to the `$DATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data`" + }, + { + "description": "This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data-recursive`", + "type": "string", + "const": "fs:allow-data-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data`", + "type": "string", + "const": "fs:allow-data-write", + "markdownDescription": "This allows non-recursive write access to the `$DATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data`" + }, + { + "description": "This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data-recursive`", + "type": "string", + "const": "fs:allow-data-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-index`", + "type": "string", + "const": "fs:allow-desktop-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-recursive`", + "type": "string", + "const": "fs:allow-desktop-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop`", + "type": "string", + "const": "fs:allow-desktop-read", + "markdownDescription": "This allows non-recursive read access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop`" + }, + { + "description": "This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop-recursive`", + "type": "string", + "const": "fs:allow-desktop-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop`", + "type": "string", + "const": "fs:allow-desktop-write", + "markdownDescription": "This allows non-recursive write access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop`" + }, + { + "description": "This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop-recursive`", + "type": "string", + "const": "fs:allow-desktop-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-index`", + "type": "string", + "const": "fs:allow-document-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-recursive`", + "type": "string", + "const": "fs:allow-document-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document`", + "type": "string", + "const": "fs:allow-document-read", + "markdownDescription": "This allows non-recursive read access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document`" + }, + { + "description": "This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document-recursive`", + "type": "string", + "const": "fs:allow-document-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document`", + "type": "string", + "const": "fs:allow-document-write", + "markdownDescription": "This allows non-recursive write access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document`" + }, + { + "description": "This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document-recursive`", + "type": "string", + "const": "fs:allow-document-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-index`", + "type": "string", + "const": "fs:allow-download-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-recursive`", + "type": "string", + "const": "fs:allow-download-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download`", + "type": "string", + "const": "fs:allow-download-read", + "markdownDescription": "This allows non-recursive read access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download`" + }, + { + "description": "This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download-recursive`", + "type": "string", + "const": "fs:allow-download-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download`", + "type": "string", + "const": "fs:allow-download-write", + "markdownDescription": "This allows non-recursive write access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download`" + }, + { + "description": "This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download-recursive`", + "type": "string", + "const": "fs:allow-download-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-index`", + "type": "string", + "const": "fs:allow-exe-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-recursive`", + "type": "string", + "const": "fs:allow-exe-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$EXE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe`", + "type": "string", + "const": "fs:allow-exe-read", + "markdownDescription": "This allows non-recursive read access to the `$EXE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe`" + }, + { + "description": "This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe-recursive`", + "type": "string", + "const": "fs:allow-exe-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$EXE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe`", + "type": "string", + "const": "fs:allow-exe-write", + "markdownDescription": "This allows non-recursive write access to the `$EXE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe`" + }, + { + "description": "This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe-recursive`", + "type": "string", + "const": "fs:allow-exe-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-index`", + "type": "string", + "const": "fs:allow-font-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-recursive`", + "type": "string", + "const": "fs:allow-font-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$FONT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font`", + "type": "string", + "const": "fs:allow-font-read", + "markdownDescription": "This allows non-recursive read access to the `$FONT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font`" + }, + { + "description": "This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font-recursive`", + "type": "string", + "const": "fs:allow-font-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$FONT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font`", + "type": "string", + "const": "fs:allow-font-write", + "markdownDescription": "This allows non-recursive write access to the `$FONT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font`" + }, + { + "description": "This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font-recursive`", + "type": "string", + "const": "fs:allow-font-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-index`", + "type": "string", + "const": "fs:allow-home-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-recursive`", + "type": "string", + "const": "fs:allow-home-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$HOME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home`", + "type": "string", + "const": "fs:allow-home-read", + "markdownDescription": "This allows non-recursive read access to the `$HOME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home`" + }, + { + "description": "This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home-recursive`", + "type": "string", + "const": "fs:allow-home-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$HOME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home`", + "type": "string", + "const": "fs:allow-home-write", + "markdownDescription": "This allows non-recursive write access to the `$HOME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home`" + }, + { + "description": "This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home-recursive`", + "type": "string", + "const": "fs:allow-home-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-index`", + "type": "string", + "const": "fs:allow-localdata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-recursive`", + "type": "string", + "const": "fs:allow-localdata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata`", + "type": "string", + "const": "fs:allow-localdata-read", + "markdownDescription": "This allows non-recursive read access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata`" + }, + { + "description": "This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata-recursive`", + "type": "string", + "const": "fs:allow-localdata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata`", + "type": "string", + "const": "fs:allow-localdata-write", + "markdownDescription": "This allows non-recursive write access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata`" + }, + { + "description": "This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata-recursive`", + "type": "string", + "const": "fs:allow-localdata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-index`", + "type": "string", + "const": "fs:allow-log-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-recursive`", + "type": "string", + "const": "fs:allow-log-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$LOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log`", + "type": "string", + "const": "fs:allow-log-read", + "markdownDescription": "This allows non-recursive read access to the `$LOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log`" + }, + { + "description": "This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log-recursive`", + "type": "string", + "const": "fs:allow-log-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$LOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log`", + "type": "string", + "const": "fs:allow-log-write", + "markdownDescription": "This allows non-recursive write access to the `$LOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log`" + }, + { + "description": "This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log-recursive`", + "type": "string", + "const": "fs:allow-log-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-index`", + "type": "string", + "const": "fs:allow-picture-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-recursive`", + "type": "string", + "const": "fs:allow-picture-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture`", + "type": "string", + "const": "fs:allow-picture-read", + "markdownDescription": "This allows non-recursive read access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture`" + }, + { + "description": "This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture-recursive`", + "type": "string", + "const": "fs:allow-picture-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture`", + "type": "string", + "const": "fs:allow-picture-write", + "markdownDescription": "This allows non-recursive write access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture`" + }, + { + "description": "This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture-recursive`", + "type": "string", + "const": "fs:allow-picture-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-index`", + "type": "string", + "const": "fs:allow-public-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-recursive`", + "type": "string", + "const": "fs:allow-public-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public`", + "type": "string", + "const": "fs:allow-public-read", + "markdownDescription": "This allows non-recursive read access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public`" + }, + { + "description": "This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public-recursive`", + "type": "string", + "const": "fs:allow-public-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public`", + "type": "string", + "const": "fs:allow-public-write", + "markdownDescription": "This allows non-recursive write access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public`" + }, + { + "description": "This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public-recursive`", + "type": "string", + "const": "fs:allow-public-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-index`", + "type": "string", + "const": "fs:allow-resource-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-recursive`", + "type": "string", + "const": "fs:allow-resource-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource`", + "type": "string", + "const": "fs:allow-resource-read", + "markdownDescription": "This allows non-recursive read access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource`" + }, + { + "description": "This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource-recursive`", + "type": "string", + "const": "fs:allow-resource-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource`", + "type": "string", + "const": "fs:allow-resource-write", + "markdownDescription": "This allows non-recursive write access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource`" + }, + { + "description": "This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource-recursive`", + "type": "string", + "const": "fs:allow-resource-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-index`", + "type": "string", + "const": "fs:allow-runtime-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-recursive`", + "type": "string", + "const": "fs:allow-runtime-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime`", + "type": "string", + "const": "fs:allow-runtime-read", + "markdownDescription": "This allows non-recursive read access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime`" + }, + { + "description": "This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime-recursive`", + "type": "string", + "const": "fs:allow-runtime-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime`", + "type": "string", + "const": "fs:allow-runtime-write", + "markdownDescription": "This allows non-recursive write access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime`" + }, + { + "description": "This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime-recursive`", + "type": "string", + "const": "fs:allow-runtime-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-index`", + "type": "string", + "const": "fs:allow-temp-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-recursive`", + "type": "string", + "const": "fs:allow-temp-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp`", + "type": "string", + "const": "fs:allow-temp-read", + "markdownDescription": "This allows non-recursive read access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp`" + }, + { + "description": "This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp-recursive`", + "type": "string", + "const": "fs:allow-temp-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp`", + "type": "string", + "const": "fs:allow-temp-write", + "markdownDescription": "This allows non-recursive write access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp`" + }, + { + "description": "This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp-recursive`", + "type": "string", + "const": "fs:allow-temp-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-index`", + "type": "string", + "const": "fs:allow-template-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-recursive`", + "type": "string", + "const": "fs:allow-template-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template`", + "type": "string", + "const": "fs:allow-template-read", + "markdownDescription": "This allows non-recursive read access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template`" + }, + { + "description": "This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template-recursive`", + "type": "string", + "const": "fs:allow-template-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template`", + "type": "string", + "const": "fs:allow-template-write", + "markdownDescription": "This allows non-recursive write access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template`" + }, + { + "description": "This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template-recursive`", + "type": "string", + "const": "fs:allow-template-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-index`", + "type": "string", + "const": "fs:allow-video-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-recursive`", + "type": "string", + "const": "fs:allow-video-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video`", + "type": "string", + "const": "fs:allow-video-read", + "markdownDescription": "This allows non-recursive read access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video`" + }, + { + "description": "This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video-recursive`", + "type": "string", + "const": "fs:allow-video-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video`", + "type": "string", + "const": "fs:allow-video-write", + "markdownDescription": "This allows non-recursive write access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video`" + }, + { + "description": "This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video-recursive`", + "type": "string", + "const": "fs:allow-video-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video-recursive`" + }, + { + "description": "This denies access to dangerous Tauri relevant files and folders by default.\n#### This permission set includes:\n\n- `deny-webview-data-linux`\n- `deny-webview-data-windows`", + "type": "string", + "const": "fs:deny-default", + "markdownDescription": "This denies access to dangerous Tauri relevant files and folders by default.\n#### This permission set includes:\n\n- `deny-webview-data-linux`\n- `deny-webview-data-windows`" + }, + { + "description": "Enables the copy_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-copy-file", + "markdownDescription": "Enables the copy_file command without any pre-configured scope." + }, + { + "description": "Enables the create command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-create", + "markdownDescription": "Enables the create command without any pre-configured scope." + }, + { + "description": "Enables the exists command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-exists", + "markdownDescription": "Enables the exists command without any pre-configured scope." + }, + { + "description": "Enables the fstat command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-fstat", + "markdownDescription": "Enables the fstat command without any pre-configured scope." + }, + { + "description": "Enables the ftruncate command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-ftruncate", + "markdownDescription": "Enables the ftruncate command without any pre-configured scope." + }, + { + "description": "Enables the lstat command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-lstat", + "markdownDescription": "Enables the lstat command without any pre-configured scope." + }, + { + "description": "Enables the mkdir command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-mkdir", + "markdownDescription": "Enables the mkdir command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Enables the read command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read", + "markdownDescription": "Enables the read command without any pre-configured scope." + }, + { + "description": "Enables the read_dir command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-dir", + "markdownDescription": "Enables the read_dir command without any pre-configured scope." + }, + { + "description": "Enables the read_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-file", + "markdownDescription": "Enables the read_file command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-text-file", + "markdownDescription": "Enables the read_text_file command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file_lines command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-text-file-lines", + "markdownDescription": "Enables the read_text_file_lines command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file_lines_next command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-text-file-lines-next", + "markdownDescription": "Enables the read_text_file_lines_next command without any pre-configured scope." + }, + { + "description": "Enables the remove command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-remove", + "markdownDescription": "Enables the remove command without any pre-configured scope." + }, + { + "description": "Enables the rename command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-rename", + "markdownDescription": "Enables the rename command without any pre-configured scope." + }, + { + "description": "Enables the seek command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-seek", + "markdownDescription": "Enables the seek command without any pre-configured scope." + }, + { + "description": "Enables the size command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-size", + "markdownDescription": "Enables the size command without any pre-configured scope." + }, + { + "description": "Enables the stat command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-stat", + "markdownDescription": "Enables the stat command without any pre-configured scope." + }, + { + "description": "Enables the truncate command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-truncate", + "markdownDescription": "Enables the truncate command without any pre-configured scope." + }, + { + "description": "Enables the unwatch command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-unwatch", + "markdownDescription": "Enables the unwatch command without any pre-configured scope." + }, + { + "description": "Enables the watch command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-watch", + "markdownDescription": "Enables the watch command without any pre-configured scope." + }, + { + "description": "Enables the write command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-write", + "markdownDescription": "Enables the write command without any pre-configured scope." + }, + { + "description": "Enables the write_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-write-file", + "markdownDescription": "Enables the write_file command without any pre-configured scope." + }, + { + "description": "Enables the write_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-write-text-file", + "markdownDescription": "Enables the write_text_file command without any pre-configured scope." + }, + { + "description": "This permissions allows to create the application specific directories.\n", + "type": "string", + "const": "fs:create-app-specific-dirs", + "markdownDescription": "This permissions allows to create the application specific directories.\n" + }, + { + "description": "Denies the copy_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-copy-file", + "markdownDescription": "Denies the copy_file command without any pre-configured scope." + }, + { + "description": "Denies the create command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-create", + "markdownDescription": "Denies the create command without any pre-configured scope." + }, + { + "description": "Denies the exists command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-exists", + "markdownDescription": "Denies the exists command without any pre-configured scope." + }, + { + "description": "Denies the fstat command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-fstat", + "markdownDescription": "Denies the fstat command without any pre-configured scope." + }, + { + "description": "Denies the ftruncate command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-ftruncate", + "markdownDescription": "Denies the ftruncate command without any pre-configured scope." + }, + { + "description": "Denies the lstat command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-lstat", + "markdownDescription": "Denies the lstat command without any pre-configured scope." + }, + { + "description": "Denies the mkdir command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-mkdir", + "markdownDescription": "Denies the mkdir command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the read command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read", + "markdownDescription": "Denies the read command without any pre-configured scope." + }, + { + "description": "Denies the read_dir command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-dir", + "markdownDescription": "Denies the read_dir command without any pre-configured scope." + }, + { + "description": "Denies the read_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-file", + "markdownDescription": "Denies the read_file command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-text-file", + "markdownDescription": "Denies the read_text_file command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file_lines command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-text-file-lines", + "markdownDescription": "Denies the read_text_file_lines command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file_lines_next command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-text-file-lines-next", + "markdownDescription": "Denies the read_text_file_lines_next command without any pre-configured scope." + }, + { + "description": "Denies the remove command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-remove", + "markdownDescription": "Denies the remove command without any pre-configured scope." + }, + { + "description": "Denies the rename command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-rename", + "markdownDescription": "Denies the rename command without any pre-configured scope." + }, + { + "description": "Denies the seek command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-seek", + "markdownDescription": "Denies the seek command without any pre-configured scope." + }, + { + "description": "Denies the size command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-size", + "markdownDescription": "Denies the size command without any pre-configured scope." + }, + { + "description": "Denies the stat command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-stat", + "markdownDescription": "Denies the stat command without any pre-configured scope." + }, + { + "description": "Denies the truncate command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-truncate", + "markdownDescription": "Denies the truncate command without any pre-configured scope." + }, + { + "description": "Denies the unwatch command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-unwatch", + "markdownDescription": "Denies the unwatch command without any pre-configured scope." + }, + { + "description": "Denies the watch command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-watch", + "markdownDescription": "Denies the watch command without any pre-configured scope." + }, + { + "description": "This denies read access to the\n`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", + "type": "string", + "const": "fs:deny-webview-data-linux", + "markdownDescription": "This denies read access to the\n`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered." + }, + { + "description": "This denies read access to the\n`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", + "type": "string", + "const": "fs:deny-webview-data-windows", + "markdownDescription": "This denies read access to the\n`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered." + }, + { + "description": "Denies the write command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-write", + "markdownDescription": "Denies the write command without any pre-configured scope." + }, + { + "description": "Denies the write_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-write-file", + "markdownDescription": "Denies the write_file command without any pre-configured scope." + }, + { + "description": "Denies the write_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-write-text-file", + "markdownDescription": "Denies the write_text_file command without any pre-configured scope." + }, + { + "description": "This enables all read related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-all", + "markdownDescription": "This enables all read related commands without any pre-configured accessible paths." + }, + { + "description": "This permission allows recursive read functionality on the application\nspecific base directories. \n", + "type": "string", + "const": "fs:read-app-specific-dirs-recursive", + "markdownDescription": "This permission allows recursive read functionality on the application\nspecific base directories. \n" + }, + { + "description": "This enables directory read and file metadata related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-dirs", + "markdownDescription": "This enables directory read and file metadata related commands without any pre-configured accessible paths." + }, + { + "description": "This enables file read related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-files", + "markdownDescription": "This enables file read related commands without any pre-configured accessible paths." + }, + { + "description": "This enables all index or metadata related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-meta", + "markdownDescription": "This enables all index or metadata related commands without any pre-configured accessible paths." + }, + { + "description": "An empty permission you can use to modify the global scope.\n\n## Example\n\n```json\n{\n \"identifier\": \"read-documents\",\n \"windows\": [\"main\"],\n \"permissions\": [\n \"fs:allow-read\",\n {\n \"identifier\": \"fs:scope\",\n \"allow\": [\n \"$APPDATA/documents/**/*\"\n ],\n \"deny\": [\n \"$APPDATA/documents/secret.txt\"\n ]\n }\n ]\n}\n```\n", + "type": "string", + "const": "fs:scope", + "markdownDescription": "An empty permission you can use to modify the global scope.\n\n## Example\n\n```json\n{\n \"identifier\": \"read-documents\",\n \"windows\": [\"main\"],\n \"permissions\": [\n \"fs:allow-read\",\n {\n \"identifier\": \"fs:scope\",\n \"allow\": [\n \"$APPDATA/documents/**/*\"\n ],\n \"deny\": [\n \"$APPDATA/documents/secret.txt\"\n ]\n }\n ]\n}\n```\n" + }, + { + "description": "This scope permits access to all files and list content of top level directories in the application folders.", + "type": "string", + "const": "fs:scope-app", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the application folders." + }, + { + "description": "This scope permits to list all files and folders in the application directories.", + "type": "string", + "const": "fs:scope-app-index", + "markdownDescription": "This scope permits to list all files and folders in the application directories." + }, + { + "description": "This scope permits recursive access to the complete application folders, including sub directories and files.", + "type": "string", + "const": "fs:scope-app-recursive", + "markdownDescription": "This scope permits recursive access to the complete application folders, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder.", + "type": "string", + "const": "fs:scope-appcache", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPCACHE`folder.", + "type": "string", + "const": "fs:scope-appcache-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPCACHE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-appcache-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder.", + "type": "string", + "const": "fs:scope-appconfig", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPCONFIG`folder.", + "type": "string", + "const": "fs:scope-appconfig-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPCONFIG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-appconfig-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPDATA` folder.", + "type": "string", + "const": "fs:scope-appdata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPDATA`folder.", + "type": "string", + "const": "fs:scope-appdata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPDATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-appdata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder.", + "type": "string", + "const": "fs:scope-applocaldata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPLOCALDATA`folder.", + "type": "string", + "const": "fs:scope-applocaldata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPLOCALDATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-applocaldata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPLOG` folder.", + "type": "string", + "const": "fs:scope-applog", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPLOG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPLOG`folder.", + "type": "string", + "const": "fs:scope-applog-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPLOG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-applog-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$AUDIO` folder.", + "type": "string", + "const": "fs:scope-audio", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$AUDIO` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$AUDIO`folder.", + "type": "string", + "const": "fs:scope-audio-index", + "markdownDescription": "This scope permits to list all files and folders in the `$AUDIO`folder." + }, + { + "description": "This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-audio-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$CACHE` folder.", + "type": "string", + "const": "fs:scope-cache", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$CACHE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$CACHE`folder.", + "type": "string", + "const": "fs:scope-cache-index", + "markdownDescription": "This scope permits to list all files and folders in the `$CACHE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-cache-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$CONFIG` folder.", + "type": "string", + "const": "fs:scope-config", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$CONFIG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$CONFIG`folder.", + "type": "string", + "const": "fs:scope-config-index", + "markdownDescription": "This scope permits to list all files and folders in the `$CONFIG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-config-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DATA` folder.", + "type": "string", + "const": "fs:scope-data", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DATA`folder.", + "type": "string", + "const": "fs:scope-data-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-data-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder.", + "type": "string", + "const": "fs:scope-desktop", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DESKTOP`folder.", + "type": "string", + "const": "fs:scope-desktop-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DESKTOP`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-desktop-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder.", + "type": "string", + "const": "fs:scope-document", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DOCUMENT`folder.", + "type": "string", + "const": "fs:scope-document-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DOCUMENT`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-document-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder.", + "type": "string", + "const": "fs:scope-download", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DOWNLOAD`folder.", + "type": "string", + "const": "fs:scope-download-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DOWNLOAD`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-download-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$EXE` folder.", + "type": "string", + "const": "fs:scope-exe", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$EXE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$EXE`folder.", + "type": "string", + "const": "fs:scope-exe-index", + "markdownDescription": "This scope permits to list all files and folders in the `$EXE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$EXE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-exe-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$EXE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$FONT` folder.", + "type": "string", + "const": "fs:scope-font", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$FONT` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$FONT`folder.", + "type": "string", + "const": "fs:scope-font-index", + "markdownDescription": "This scope permits to list all files and folders in the `$FONT`folder." + }, + { + "description": "This scope permits recursive access to the complete `$FONT` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-font-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$FONT` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$HOME` folder.", + "type": "string", + "const": "fs:scope-home", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$HOME` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$HOME`folder.", + "type": "string", + "const": "fs:scope-home-index", + "markdownDescription": "This scope permits to list all files and folders in the `$HOME`folder." + }, + { + "description": "This scope permits recursive access to the complete `$HOME` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-home-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$HOME` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder.", + "type": "string", + "const": "fs:scope-localdata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$LOCALDATA`folder.", + "type": "string", + "const": "fs:scope-localdata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$LOCALDATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-localdata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$LOG` folder.", + "type": "string", + "const": "fs:scope-log", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$LOG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$LOG`folder.", + "type": "string", + "const": "fs:scope-log-index", + "markdownDescription": "This scope permits to list all files and folders in the `$LOG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$LOG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-log-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$LOG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$PICTURE` folder.", + "type": "string", + "const": "fs:scope-picture", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$PICTURE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$PICTURE`folder.", + "type": "string", + "const": "fs:scope-picture-index", + "markdownDescription": "This scope permits to list all files and folders in the `$PICTURE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-picture-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder.", + "type": "string", + "const": "fs:scope-public", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$PUBLIC`folder.", + "type": "string", + "const": "fs:scope-public-index", + "markdownDescription": "This scope permits to list all files and folders in the `$PUBLIC`folder." + }, + { + "description": "This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-public-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder.", + "type": "string", + "const": "fs:scope-resource", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$RESOURCE`folder.", + "type": "string", + "const": "fs:scope-resource-index", + "markdownDescription": "This scope permits to list all files and folders in the `$RESOURCE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-resource-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder.", + "type": "string", + "const": "fs:scope-runtime", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$RUNTIME`folder.", + "type": "string", + "const": "fs:scope-runtime-index", + "markdownDescription": "This scope permits to list all files and folders in the `$RUNTIME`folder." + }, + { + "description": "This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-runtime-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$TEMP` folder.", + "type": "string", + "const": "fs:scope-temp", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$TEMP` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$TEMP`folder.", + "type": "string", + "const": "fs:scope-temp-index", + "markdownDescription": "This scope permits to list all files and folders in the `$TEMP`folder." + }, + { + "description": "This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-temp-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder.", + "type": "string", + "const": "fs:scope-template", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$TEMPLATE`folder.", + "type": "string", + "const": "fs:scope-template-index", + "markdownDescription": "This scope permits to list all files and folders in the `$TEMPLATE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-template-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$VIDEO` folder.", + "type": "string", + "const": "fs:scope-video", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$VIDEO` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$VIDEO`folder.", + "type": "string", + "const": "fs:scope-video-index", + "markdownDescription": "This scope permits to list all files and folders in the `$VIDEO`folder." + }, + { + "description": "This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-video-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files." + }, + { + "description": "This enables all write related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:write-all", + "markdownDescription": "This enables all write related commands without any pre-configured accessible paths." + }, + { + "description": "This enables all file write related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:write-files", + "markdownDescription": "This enables all file write related commands without any pre-configured accessible paths." + } + ] + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + } + } +} \ No newline at end of file diff --git a/apps/desktop/src-tauri/src/git.rs b/apps/desktop/src-tauri/src/git.rs index d74f0e5..785398f 100644 --- a/apps/desktop/src-tauri/src/git.rs +++ b/apps/desktop/src-tauri/src/git.rs @@ -62,9 +62,9 @@ pub fn open_repo(path: &str) -> Result { .branches(Some(git2::BranchType::Local)) .map_err(|e| format!("Failed to list branches: {}", e))? .filter_map(|branch_result| { - branch_result.ok().and_then(|(branch, _)| { - branch.name().ok().flatten().map(|name| name.to_string()) - }) + branch_result + .ok() + .and_then(|(branch, _)| branch.name().ok().flatten().map(|name| name.to_string())) }) .collect(); @@ -163,11 +163,15 @@ pub fn git_diff(path: &str, base: &str, compare: &str) -> Result Result { if invert_changes { (old_path.unwrap_or_default(), "add", None) } else { (old_path.unwrap_or_default(), "remove", None) } - }, - git2::Delta::Modified => { - (new_path.unwrap_or_default(), "modify", None) - }, + } + git2::Delta::Modified => (new_path.unwrap_or_default(), "modify", None), git2::Delta::Renamed => { // For renames, store the new path as primary and old path separately (new_path.clone().unwrap_or_default(), "rename", old_path) - }, + } git2::Delta::Copied => { // For copies, store the new path as primary and old path separately (new_path.clone().unwrap_or_default(), "copy", old_path) - }, - _ => { - (new_path.or(old_path).unwrap_or_default(), "modify", None) - }, + } + _ => (new_path.or(old_path).unwrap_or_default(), "modify", None), }; files.push(DiffFile { @@ -350,7 +350,11 @@ pub fn resolve_ref(path: &str, ref_name: &str) -> Result Result { +pub fn read_file_blob( + path: &str, + ref_name: &str, + file_path: &str, +) -> Result { // Handle WORKDIR sentinel - read from filesystem if ref_name == WORKDIR_SENTINEL { use std::fs; @@ -362,7 +366,7 @@ pub fn read_file_blob(path: &str, ref_name: &str, file_path: &str) -> Result { // Check if binary (scan first 8KB for null bytes) let check_len = content.len().min(8192); - let is_binary = content[..check_len].iter().any(|&b| b == 0); + let is_binary = content[..check_len].contains(&0); if is_binary { return Ok(ReadFileResult { @@ -381,75 +385,72 @@ pub fn read_file_blob(path: &str, ref_name: &str, file_path: &str) -> Result { - Ok(ReadFileResult { - binary: false, - text: None, - not_found: Some(true), - }) - } + Err(_) => Ok(ReadFileResult { + binary: false, + text: None, + not_found: Some(true), + }), } } else { // Normal Git blob reading - let repo = Repository::open(path).map_err(|e| format!("Failed to open repository: {}", e))?; + let repo = + Repository::open(path).map_err(|e| format!("Failed to open repository: {}", e))?; - // Resolve ref to commit - let commit = repo - .revparse_single(ref_name) - .map_err(|e| format!("Failed to resolve ref '{}': {}", ref_name, e))? - .peel_to_commit() - .map_err(|e| format!("Failed to peel to commit: {}", e))?; + // Resolve ref to commit + let commit = repo + .revparse_single(ref_name) + .map_err(|e| format!("Failed to resolve ref '{}': {}", ref_name, e))? + .peel_to_commit() + .map_err(|e| format!("Failed to peel to commit: {}", e))?; - // Get tree from commit - let tree = commit - .tree() - .map_err(|e| format!("Failed to get tree: {}", e))?; + // Get tree from commit + let tree = commit + .tree() + .map_err(|e| format!("Failed to get tree: {}", e))?; - // Find the file in the tree - let entry = tree - .get_path(Path::new(file_path)) - .map_err(|_| { - return ReadFileResult { + // Find the file in the tree + let entry = tree + .get_path(Path::new(file_path)) + .map_err(|_| ReadFileResult { binary: false, text: None, not_found: Some(true), - }; - }) - .ok(); + }) + .ok(); - if entry.is_none() { - return Ok(ReadFileResult { - binary: false, - text: None, - not_found: Some(true), - }); - } + if entry.is_none() { + return Ok(ReadFileResult { + binary: false, + text: None, + not_found: Some(true), + }); + } - let entry = entry.unwrap(); + let entry = entry.unwrap(); - // Get the blob - let object = entry - .to_object(&repo) - .map_err(|e| format!("Failed to get object: {}", e))?; + // Get the blob + let object = entry + .to_object(&repo) + .map_err(|e| format!("Failed to get object: {}", e))?; - let blob = object - .as_blob() - .ok_or_else(|| format!("Path is not a file"))?; + let blob = object + .as_blob() + .ok_or_else(|| "Path is not a file".to_string())?; - // Check if binary - let content = blob.content(); - let is_binary = content.iter().any(|&b| b == 0); + // Check if binary + let content = blob.content(); + let is_binary = content.contains(&0); - if is_binary { - return Ok(ReadFileResult { - binary: true, - text: None, - not_found: None, - }); - } + if is_binary { + return Ok(ReadFileResult { + binary: true, + text: None, + not_found: None, + }); + } - // Convert to UTF-8 string (use lossy conversion for non-UTF8 encodings like Latin-1) - let text = String::from_utf8_lossy(content).into_owned(); + // Convert to UTF-8 string (use lossy conversion for non-UTF8 encodings like Latin-1) + let text = String::from_utf8_lossy(content).into_owned(); Ok(ReadFileResult { binary: false, @@ -462,15 +463,921 @@ pub fn read_file_blob(path: &str, ref_name: &str, file_path: &str) -> Result (tempfile::TempDir, String) { + let tmp = tempfile::tempdir().expect("failed to create temp dir"); + let repo_path = tmp.path().to_str().unwrap().to_string(); + + let repo = git2::Repository::init(&repo_path).expect("failed to init repo"); + + // Configure committer identity for the test repo + let mut config = repo.config().expect("failed to get config"); + config.set_str("user.name", "Test").unwrap(); + config.set_str("user.email", "test@test.com").unwrap(); + + // Create an initial commit so "main" branch exists + let sig = repo.signature().unwrap(); + let tree_id = { + let mut index = repo.index().unwrap(); + // Write an initial file so the tree is non-empty + let file_path = tmp.path().join("README.md"); + fs::write(&file_path, "# Test Repo\n").unwrap(); + index.add_path(std::path::Path::new("README.md")).unwrap(); + index.write().unwrap(); + index.write_tree().unwrap() + }; + let tree = repo.find_tree(tree_id).unwrap(); + repo.commit( + Some("refs/heads/main"), + &sig, + &sig, + "initial commit", + &tree, + &[], + ) + .unwrap(); + + // Set HEAD to main + repo.set_head("refs/heads/main").unwrap(); + + (tmp, repo_path) + } - // Note: These tests would need a test repository to run against - // For now, they serve as examples of how to test the git operations + // ── open_repo tests ─────────────────────────────────────────────── #[test] - fn test_open_repo_structure() { - // Test that the function signature is correct - // Actual test would need a valid repo path + fn test_open_repo_invalid_path() { let result = open_repo("/nonexistent/path"); assert!(result.is_err()); } + + // ── git_diff tests ──────────────────────────────────────────────── + + #[test] + fn test_diff_identical_branches_returns_empty() { + let (_tmp, repo_path) = create_test_repo(); + + // Diff main against itself should produce no changes + let result = git_diff(&repo_path, "main", "main").unwrap(); + assert!( + result.files.is_empty(), + "identical branches should produce empty diff" + ); + } + + #[test] + fn test_diff_detects_added_files() { + let (_tmp, repo_path) = create_test_repo(); + let repo = git2::Repository::open(&repo_path).unwrap(); + let sig = repo.signature().unwrap(); + + // Create a feature branch from main + let main_commit = repo + .revparse_single("main") + .unwrap() + .peel_to_commit() + .unwrap(); + repo.branch("feature-add", &main_commit, false).unwrap(); + + // Checkout feature branch and add a new file + repo.set_head("refs/heads/feature-add").unwrap(); + repo.checkout_head(Some(git2::build::CheckoutBuilder::new().force())) + .unwrap(); + + let new_file = std::path::Path::new(&repo_path).join("new_file.txt"); + fs::write(&new_file, "new content\n").unwrap(); + + let mut index = repo.index().unwrap(); + index + .add_path(std::path::Path::new("new_file.txt")) + .unwrap(); + index.write().unwrap(); + let tree_id = index.write_tree().unwrap(); + let tree = repo.find_tree(tree_id).unwrap(); + repo.commit( + Some("refs/heads/feature-add"), + &sig, + &sig, + "add new file", + &tree, + &[&main_commit], + ) + .unwrap(); + + let result = git_diff(&repo_path, "main", "feature-add").unwrap(); + assert_eq!(result.files.len(), 1); + assert_eq!(result.files[0].path, "new_file.txt"); + assert_eq!(result.files[0].change_type, "add"); + } + + #[test] + fn test_diff_detects_modified_files() { + let (_tmp, repo_path) = create_test_repo(); + let repo = git2::Repository::open(&repo_path).unwrap(); + let sig = repo.signature().unwrap(); + + let main_commit = repo + .revparse_single("main") + .unwrap() + .peel_to_commit() + .unwrap(); + repo.branch("feature-modify", &main_commit, false).unwrap(); + repo.set_head("refs/heads/feature-modify").unwrap(); + repo.checkout_head(Some(git2::build::CheckoutBuilder::new().force())) + .unwrap(); + + // Modify README.md + let readme = std::path::Path::new(&repo_path).join("README.md"); + fs::write(&readme, "# Modified Repo\nSome extra content.\n").unwrap(); + + let mut index = repo.index().unwrap(); + index.add_path(std::path::Path::new("README.md")).unwrap(); + index.write().unwrap(); + let tree_id = index.write_tree().unwrap(); + let tree = repo.find_tree(tree_id).unwrap(); + repo.commit( + Some("refs/heads/feature-modify"), + &sig, + &sig, + "modify readme", + &tree, + &[&main_commit], + ) + .unwrap(); + + let result = git_diff(&repo_path, "main", "feature-modify").unwrap(); + assert_eq!(result.files.len(), 1); + assert_eq!(result.files[0].path, "README.md"); + assert_eq!(result.files[0].change_type, "modify"); + } + + #[test] + fn test_diff_detects_removed_files() { + let (_tmp, repo_path) = create_test_repo(); + let repo = git2::Repository::open(&repo_path).unwrap(); + let sig = repo.signature().unwrap(); + + let main_commit = repo + .revparse_single("main") + .unwrap() + .peel_to_commit() + .unwrap(); + repo.branch("feature-remove", &main_commit, false).unwrap(); + repo.set_head("refs/heads/feature-remove").unwrap(); + repo.checkout_head(Some(git2::build::CheckoutBuilder::new().force())) + .unwrap(); + + // Remove README.md + let readme = std::path::Path::new(&repo_path).join("README.md"); + fs::remove_file(&readme).unwrap(); + + let mut index = repo.index().unwrap(); + index + .remove_path(std::path::Path::new("README.md")) + .unwrap(); + index.write().unwrap(); + let tree_id = index.write_tree().unwrap(); + let tree = repo.find_tree(tree_id).unwrap(); + repo.commit( + Some("refs/heads/feature-remove"), + &sig, + &sig, + "remove readme", + &tree, + &[&main_commit], + ) + .unwrap(); + + let result = git_diff(&repo_path, "main", "feature-remove").unwrap(); + assert_eq!(result.files.len(), 1); + assert_eq!(result.files[0].path, "README.md"); + assert_eq!(result.files[0].change_type, "remove"); + } + + #[test] + fn test_diff_detects_renamed_files() { + let (_tmp, repo_path) = create_test_repo(); + let repo = git2::Repository::open(&repo_path).unwrap(); + let sig = repo.signature().unwrap(); + + let main_commit = repo + .revparse_single("main") + .unwrap() + .peel_to_commit() + .unwrap(); + repo.branch("feature-rename", &main_commit, false).unwrap(); + repo.set_head("refs/heads/feature-rename").unwrap(); + repo.checkout_head(Some(git2::build::CheckoutBuilder::new().force())) + .unwrap(); + + // Rename README.md → DOCS.md (same content so find_similar detects it) + let old_path = std::path::Path::new(&repo_path).join("README.md"); + let new_path = std::path::Path::new(&repo_path).join("DOCS.md"); + fs::rename(&old_path, &new_path).unwrap(); + + let mut index = repo.index().unwrap(); + index + .remove_path(std::path::Path::new("README.md")) + .unwrap(); + index.add_path(std::path::Path::new("DOCS.md")).unwrap(); + index.write().unwrap(); + let tree_id = index.write_tree().unwrap(); + let tree = repo.find_tree(tree_id).unwrap(); + repo.commit( + Some("refs/heads/feature-rename"), + &sig, + &sig, + "rename readme to docs", + &tree, + &[&main_commit], + ) + .unwrap(); + + let result = git_diff(&repo_path, "main", "feature-rename").unwrap(); + assert_eq!( + result.files.len(), + 1, + "rename should produce exactly 1 diff entry" + ); + assert_eq!(result.files[0].change_type, "rename"); + assert_eq!(result.files[0].path, "DOCS.md"); + assert_eq!( + result.files[0].old_path.as_deref(), + Some("README.md"), + "old_path should contain the original filename" + ); + } + + #[test] + fn test_diff_workdir_detects_uncommitted_changes() { + let (_tmp, repo_path) = create_test_repo(); + let repo = git2::Repository::open(&repo_path).unwrap(); + + // Checkout main so working directory matches + repo.set_head("refs/heads/main").unwrap(); + repo.checkout_head(Some(git2::build::CheckoutBuilder::new().force())) + .unwrap(); + + // Modify README.md in working directory (do NOT commit) + let readme = std::path::Path::new(&repo_path).join("README.md"); + fs::write(&readme, "# Changed in workdir\n").unwrap(); + + let result = git_diff(&repo_path, "main", WORKDIR_SENTINEL).unwrap(); + assert!( + !result.files.is_empty(), + "WORKDIR diff should detect uncommitted changes" + ); + + let modified = result.files.iter().find(|f| f.path == "README.md"); + assert!(modified.is_some(), "should find modified README.md"); + assert_eq!(modified.unwrap().change_type, "modify"); + } + + #[test] + fn test_diff_nonexistent_branch_returns_error() { + let (_tmp, repo_path) = create_test_repo(); + + let result = git_diff(&repo_path, "main", "nonexistent-branch"); + assert!( + result.is_err(), + "diffing against a nonexistent branch should error" + ); + let err_msg = result.unwrap_err(); + assert!( + err_msg.contains("Failed to resolve"), + "error should mention resolution failure, got: {}", + err_msg + ); + } + + #[test] + fn test_diff_binary_files_detected() { + let (_tmp, repo_path) = create_test_repo(); + let repo = git2::Repository::open(&repo_path).unwrap(); + let sig = repo.signature().unwrap(); + + let main_commit = repo + .revparse_single("main") + .unwrap() + .peel_to_commit() + .unwrap(); + repo.branch("feature-binary", &main_commit, false).unwrap(); + repo.set_head("refs/heads/feature-binary").unwrap(); + repo.checkout_head(Some(git2::build::CheckoutBuilder::new().force())) + .unwrap(); + + // Add a binary file (contains null bytes) + let bin_path = std::path::Path::new(&repo_path).join("image.png"); + let binary_content: Vec = vec![0x89, 0x50, 0x4E, 0x47, 0x00, 0x00, 0x00, 0x01]; + fs::write(&bin_path, &binary_content).unwrap(); + + let mut index = repo.index().unwrap(); + index.add_path(std::path::Path::new("image.png")).unwrap(); + index.write().unwrap(); + let tree_id = index.write_tree().unwrap(); + let tree = repo.find_tree(tree_id).unwrap(); + repo.commit( + Some("refs/heads/feature-binary"), + &sig, + &sig, + "add binary file", + &tree, + &[&main_commit], + ) + .unwrap(); + + // The diff should detect the binary file as an added file + let result = git_diff(&repo_path, "main", "feature-binary").unwrap(); + let bin_file = result.files.iter().find(|f| f.path == "image.png"); + assert!(bin_file.is_some(), "binary file should appear in diff"); + assert_eq!(bin_file.unwrap().change_type, "add"); + + // Also verify read_file_blob marks it as binary + let blob = read_file_blob(&repo_path, "feature-binary", "image.png").unwrap(); + assert!( + blob.binary, + "image.png should be detected as binary by read_file_blob" + ); + } + + // ── read_file_blob tests ───────────────────────────────────────── + + #[test] + fn test_read_file_blob_text_file_returns_correct_content() { + let (_tmp, repo_path) = create_test_repo(); + + // README.md was committed in the initial commit with "# Test Repo\n" + let result = read_file_blob(&repo_path, "main", "README.md").unwrap(); + assert!(!result.binary, "README.md should not be binary"); + assert_eq!(result.not_found, None, "not_found should be None"); + assert_eq!( + result.text.as_deref(), + Some("# Test Repo\n"), + "text content should match what was committed" + ); + } + + #[test] + fn test_read_file_blob_missing_file_returns_not_found() { + let (_tmp, repo_path) = create_test_repo(); + + let result = read_file_blob(&repo_path, "main", "does_not_exist.txt").unwrap(); + assert_eq!( + result.not_found, + Some(true), + "missing file should have notFound=true" + ); + assert!(!result.binary, "missing file should not be marked binary"); + assert_eq!(result.text, None, "missing file should have no text"); + } + + #[test] + fn test_read_file_blob_binary_file_returns_binary_true() { + let (_tmp, repo_path) = create_test_repo(); + let repo = git2::Repository::open(&repo_path).unwrap(); + let sig = repo.signature().unwrap(); + + let main_commit = repo + .revparse_single("main") + .unwrap() + .peel_to_commit() + .unwrap(); + + // Create a branch with a binary file containing null bytes + repo.branch("binary-branch", &main_commit, false).unwrap(); + repo.set_head("refs/heads/binary-branch").unwrap(); + repo.checkout_head(Some(git2::build::CheckoutBuilder::new().force())) + .unwrap(); + + let bin_path = std::path::Path::new(&repo_path).join("data.bin"); + let binary_content: Vec = vec![0xFF, 0xD8, 0x00, 0x00, 0x01, 0x02, 0x03]; + fs::write(&bin_path, &binary_content).unwrap(); + + let mut index = repo.index().unwrap(); + index.add_path(std::path::Path::new("data.bin")).unwrap(); + index.write().unwrap(); + let tree_id = index.write_tree().unwrap(); + let tree = repo.find_tree(tree_id).unwrap(); + repo.commit( + Some("refs/heads/binary-branch"), + &sig, + &sig, + "add binary file", + &tree, + &[&main_commit], + ) + .unwrap(); + + let result = read_file_blob(&repo_path, "binary-branch", "data.bin").unwrap(); + assert!( + result.binary, + "file with null bytes should be detected as binary" + ); + assert_eq!(result.text, None, "binary file should have no text"); + assert_eq!( + result.not_found, None, + "binary file should not be not_found" + ); + } + + #[test] + fn test_read_file_blob_workdir_reads_from_filesystem() { + let (_tmp, repo_path) = create_test_repo(); + let repo = git2::Repository::open(&repo_path).unwrap(); + + // Checkout main so working directory is populated + repo.set_head("refs/heads/main").unwrap(); + repo.checkout_head(Some(git2::build::CheckoutBuilder::new().force())) + .unwrap(); + + // Write a new file to the working directory (not committed) + let new_file = std::path::Path::new(&repo_path).join("workdir_only.txt"); + fs::write(&new_file, "workdir content here\n").unwrap(); + + let result = read_file_blob(&repo_path, WORKDIR_SENTINEL, "workdir_only.txt").unwrap(); + assert!(!result.binary, "text file in workdir should not be binary"); + assert_eq!( + result.not_found, None, + "file exists so not_found should be None" + ); + assert_eq!( + result.text.as_deref(), + Some("workdir content here\n"), + "WORKDIR read should return filesystem content" + ); + } + + #[test] + fn test_read_file_blob_workdir_missing_file_returns_not_found() { + let (_tmp, repo_path) = create_test_repo(); + + let result = read_file_blob(&repo_path, WORKDIR_SENTINEL, "no_such_file.txt").unwrap(); + assert_eq!( + result.not_found, + Some(true), + "missing workdir file should have notFound=true" + ); + assert!(!result.binary); + assert_eq!(result.text, None); + } + + #[test] + fn test_read_file_blob_non_utf8_lossy_conversion() { + let (_tmp, repo_path) = create_test_repo(); + let repo = git2::Repository::open(&repo_path).unwrap(); + let sig = repo.signature().unwrap(); + + let main_commit = repo + .revparse_single("main") + .unwrap() + .peel_to_commit() + .unwrap(); + + repo.branch("non-utf8-branch", &main_commit, false).unwrap(); + repo.set_head("refs/heads/non-utf8-branch").unwrap(); + repo.checkout_head(Some(git2::build::CheckoutBuilder::new().force())) + .unwrap(); + + // Create a file with invalid UTF-8 bytes (but no null bytes, so not binary) + // 0xC0 0xC1 are invalid UTF-8 lead bytes; 0xFE 0xFF are also invalid + let invalid_utf8: Vec = vec![ + b'h', b'e', b'l', b'l', b'o', 0xC0, 0xC1, b'w', b'o', b'r', b'l', b'd', + ]; + let file_path = std::path::Path::new(&repo_path).join("bad_utf8.txt"); + fs::write(&file_path, &invalid_utf8).unwrap(); + + let mut index = repo.index().unwrap(); + index + .add_path(std::path::Path::new("bad_utf8.txt")) + .unwrap(); + index.write().unwrap(); + let tree_id = index.write_tree().unwrap(); + let tree = repo.find_tree(tree_id).unwrap(); + repo.commit( + Some("refs/heads/non-utf8-branch"), + &sig, + &sig, + "add non-utf8 file", + &tree, + &[&main_commit], + ) + .unwrap(); + + let result = read_file_blob(&repo_path, "non-utf8-branch", "bad_utf8.txt").unwrap(); + assert!( + !result.binary, + "non-UTF8 text file should not be binary (no null bytes)" + ); + assert_eq!(result.not_found, None); + + let text = result.text.expect("should have text via lossy conversion"); + // Lossy conversion replaces invalid bytes with U+FFFD (�) + assert!( + text.contains('\u{FFFD}'), + "lossy conversion should produce replacement characters, got: {:?}", + text + ); + assert!( + text.starts_with("hello"), + "valid prefix should be preserved" + ); + assert!(text.ends_with("world"), "valid suffix should be preserved"); + } + + #[test] + fn test_read_file_blob_non_utf8_workdir_lossy_conversion() { + let (_tmp, repo_path) = create_test_repo(); + let repo = git2::Repository::open(&repo_path).unwrap(); + + repo.set_head("refs/heads/main").unwrap(); + repo.checkout_head(Some(git2::build::CheckoutBuilder::new().force())) + .unwrap(); + + // Write invalid UTF-8 directly to workdir (no null bytes) + let invalid_utf8: Vec = vec![b'A', b'B', 0xFE, 0xFF, b'C', b'D']; + let file_path = std::path::Path::new(&repo_path).join("bad_workdir.txt"); + fs::write(&file_path, &invalid_utf8).unwrap(); + + let result = read_file_blob(&repo_path, WORKDIR_SENTINEL, "bad_workdir.txt").unwrap(); + assert!(!result.binary); + let text = result.text.expect("should have text via lossy conversion"); + assert!( + text.contains('\u{FFFD}'), + "WORKDIR lossy conversion should produce replacement characters" + ); + assert!(text.starts_with("AB"), "valid prefix should be preserved"); + assert!(text.ends_with("CD"), "valid suffix should be preserved"); + } + + #[test] + fn test_read_file_blob_bad_ref_returns_error() { + let (_tmp, repo_path) = create_test_repo(); + + let result = read_file_blob(&repo_path, "nonexistent-ref", "README.md"); + assert!( + result.is_err(), + "reading from a non-existent ref should return an error" + ); + let err_msg = result.unwrap_err(); + assert!( + err_msg.contains("Failed to resolve"), + "error should mention resolution failure, got: {}", + err_msg + ); + } + + #[test] + fn test_read_file_blob_workdir_binary_file_detected() { + let (_tmp, repo_path) = create_test_repo(); + let repo = git2::Repository::open(&repo_path).unwrap(); + + repo.set_head("refs/heads/main").unwrap(); + repo.checkout_head(Some(git2::build::CheckoutBuilder::new().force())) + .unwrap(); + + // Write a binary file (containing null bytes) to the working directory + let bin_content: Vec = vec![0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x00, 0x1A]; + let bin_path = std::path::Path::new(&repo_path).join("photo.png"); + fs::write(&bin_path, &bin_content).unwrap(); + + let result = read_file_blob(&repo_path, WORKDIR_SENTINEL, "photo.png").unwrap(); + assert!( + result.binary, + "WORKDIR binary file (null bytes) should be detected" + ); + assert_eq!(result.text, None, "binary workdir file should have no text"); + assert_eq!(result.not_found, None); + } + + // ── list_files tests ──────────────────────────────────────────────── + + #[test] + fn test_list_files_returns_all_committed_files() { + let (_tmp, repo_path) = create_test_repo(); + + // The test repo has only README.md committed on main + let result = list_files(&repo_path, "main").unwrap(); + assert_eq!(result.files, vec!["README.md"]); + } + + #[test] + fn test_list_files_nested_directories_flatten_to_full_paths() { + let (_tmp, repo_path) = create_test_repo(); + let repo = git2::Repository::open(&repo_path).unwrap(); + let sig = repo.signature().unwrap(); + + let main_commit = repo + .revparse_single("main") + .unwrap() + .peel_to_commit() + .unwrap(); + repo.branch("feature-nested", &main_commit, false).unwrap(); + repo.set_head("refs/heads/feature-nested").unwrap(); + repo.checkout_head(Some(git2::build::CheckoutBuilder::new().force())) + .unwrap(); + + // Create nested directory structure: src/lib/util.rs and src/main.rs + let src_dir = std::path::Path::new(&repo_path).join("src"); + let lib_dir = src_dir.join("lib"); + fs::create_dir_all(&lib_dir).unwrap(); + fs::write(src_dir.join("main.rs"), "fn main() {}\n").unwrap(); + fs::write(lib_dir.join("util.rs"), "pub fn helper() {}\n").unwrap(); + + let mut index = repo.index().unwrap(); + index.add_path(std::path::Path::new("src/main.rs")).unwrap(); + index + .add_path(std::path::Path::new("src/lib/util.rs")) + .unwrap(); + index.write().unwrap(); + let tree_id = index.write_tree().unwrap(); + let tree = repo.find_tree(tree_id).unwrap(); + repo.commit( + Some("refs/heads/feature-nested"), + &sig, + &sig, + "add nested files", + &tree, + &[&main_commit], + ) + .unwrap(); + + let result = list_files(&repo_path, "feature-nested").unwrap(); + let mut files = result.files.clone(); + files.sort(); + + assert_eq!( + files, + vec!["README.md", "src/lib/util.rs", "src/main.rs"], + "nested directories should be flattened to full paths with forward slashes" + ); + } + + #[test] + fn test_list_files_empty_tree_returns_empty_list() { + let tmp = tempfile::tempdir().expect("failed to create temp dir"); + let repo_path = tmp.path().to_str().unwrap().to_string(); + + let repo = git2::Repository::init(&repo_path).expect("failed to init repo"); + + // Configure committer identity + let mut config = repo.config().expect("failed to get config"); + config.set_str("user.name", "Test").unwrap(); + config.set_str("user.email", "test@test.com").unwrap(); + + // Create a commit with an empty tree (no files) + let sig = repo.signature().unwrap(); + let empty_tree_id = repo.treebuilder(None).unwrap().write().unwrap(); + let empty_tree = repo.find_tree(empty_tree_id).unwrap(); + repo.commit( + Some("refs/heads/main"), + &sig, + &sig, + "empty commit", + &empty_tree, + &[], + ) + .unwrap(); + repo.set_head("refs/heads/main").unwrap(); + + let result = list_files(&repo_path, "main").unwrap(); + assert!( + result.files.is_empty(), + "empty tree should return empty file list" + ); + } + + #[test] + fn test_list_files_workdir_returns_filesystem_files() { + let (_tmp, repo_path) = create_test_repo(); + let repo = git2::Repository::open(&repo_path).unwrap(); + + repo.set_head("refs/heads/main").unwrap(); + repo.checkout_head(Some(git2::build::CheckoutBuilder::new().force())) + .unwrap(); + + // Add an extra untracked file to the working directory + let extra_path = std::path::Path::new(&repo_path).join("extra.txt"); + fs::write(&extra_path, "extra content\n").unwrap(); + + let result = list_files(&repo_path, WORKDIR_SENTINEL).unwrap(); + assert!( + result.files.contains(&"README.md".to_string()), + "WORKDIR should include tracked files" + ); + assert!( + result.files.contains(&"extra.txt".to_string()), + "WORKDIR should include untracked files" + ); + } + + #[test] + fn test_list_files_workdir_respects_gitignore() { + let (_tmp, repo_path) = create_test_repo(); + let repo = git2::Repository::open(&repo_path).unwrap(); + let sig = repo.signature().unwrap(); + + let main_commit = repo + .revparse_single("main") + .unwrap() + .peel_to_commit() + .unwrap(); + + // Add a .gitignore that ignores *.log files + let gitignore_path = std::path::Path::new(&repo_path).join(".gitignore"); + fs::write(&gitignore_path, "*.log\nbuild/\n").unwrap(); + + let mut index = repo.index().unwrap(); + index.add_path(std::path::Path::new(".gitignore")).unwrap(); + index.write().unwrap(); + let tree_id = index.write_tree().unwrap(); + let tree = repo.find_tree(tree_id).unwrap(); + repo.commit( + Some("refs/heads/main"), + &sig, + &sig, + "add gitignore", + &tree, + &[&main_commit], + ) + .unwrap(); + + repo.set_head("refs/heads/main").unwrap(); + repo.checkout_head(Some(git2::build::CheckoutBuilder::new().force())) + .unwrap(); + + // Create files: one ignored, one not + let log_path = std::path::Path::new(&repo_path).join("debug.log"); + fs::write(&log_path, "log line\n").unwrap(); + + let build_dir = std::path::Path::new(&repo_path).join("build"); + fs::create_dir_all(&build_dir).unwrap(); + fs::write(build_dir.join("output.js"), "var x = 1;\n").unwrap(); + + let src_path = std::path::Path::new(&repo_path).join("src.rs"); + fs::write(&src_path, "fn main() {}\n").unwrap(); + + let result = list_files(&repo_path, WORKDIR_SENTINEL).unwrap(); + assert!( + result.files.contains(&"src.rs".to_string()), + "non-ignored files should be listed" + ); + assert!( + !result.files.contains(&"debug.log".to_string()), + ".gitignore should exclude *.log files" + ); + assert!( + !result.files.contains(&"build/output.js".to_string()), + ".gitignore should exclude build/ directory" + ); + } + + // ── list_files_with_oids tests ────────────────────────────────────── + + #[test] + fn test_list_files_with_oids_returns_valid_oid_strings() { + let (_tmp, repo_path) = create_test_repo(); + + let result = list_files_with_oids(&repo_path, "main").unwrap(); + assert_eq!( + result.files.len(), + 1, + "test repo has one committed file on main" + ); + + let file = &result.files[0]; + assert_eq!(file.path, "README.md"); + // OIDs are 40-character hex strings + assert_eq!( + file.oid.len(), + 40, + "OID should be 40 characters, got: {}", + file.oid + ); + assert!( + file.oid.chars().all(|c| c.is_ascii_hexdigit()), + "OID should be valid hex string, got: {}", + file.oid + ); + } + + #[test] + fn test_list_files_with_oids_multiple_files() { + let (_tmp, repo_path) = create_test_repo(); + let repo = git2::Repository::open(&repo_path).unwrap(); + let sig = repo.signature().unwrap(); + + let main_commit = repo + .revparse_single("main") + .unwrap() + .peel_to_commit() + .unwrap(); + repo.branch("multi-files", &main_commit, false).unwrap(); + repo.set_head("refs/heads/multi-files").unwrap(); + repo.checkout_head(Some(git2::build::CheckoutBuilder::new().force())) + .unwrap(); + + // Add multiple files + fs::write(std::path::Path::new(&repo_path).join("a.txt"), "file a\n").unwrap(); + fs::write(std::path::Path::new(&repo_path).join("b.txt"), "file b\n").unwrap(); + + let mut index = repo.index().unwrap(); + index.add_path(std::path::Path::new("a.txt")).unwrap(); + index.add_path(std::path::Path::new("b.txt")).unwrap(); + index.write().unwrap(); + let tree_id = index.write_tree().unwrap(); + let tree = repo.find_tree(tree_id).unwrap(); + repo.commit( + Some("refs/heads/multi-files"), + &sig, + &sig, + "add multiple files", + &tree, + &[&main_commit], + ) + .unwrap(); + + let result = list_files_with_oids(&repo_path, "multi-files").unwrap(); + let mut paths: Vec<&str> = result.files.iter().map(|f| f.path.as_str()).collect(); + paths.sort(); + assert_eq!(paths, vec!["README.md", "a.txt", "b.txt"]); + + // All OIDs should be valid 40-char hex + for file in &result.files { + assert_eq!( + file.oid.len(), + 40, + "OID for {} should be 40 chars", + file.path + ); + assert!( + file.oid.chars().all(|c| c.is_ascii_hexdigit()), + "OID for {} should be valid hex", + file.path + ); + } + + // Different file contents should have different OIDs + let oid_a = result.files.iter().find(|f| f.path == "a.txt").unwrap(); + let oid_b = result.files.iter().find(|f| f.path == "b.txt").unwrap(); + assert_ne!( + oid_a.oid, oid_b.oid, + "files with different content should have different OIDs" + ); + } + + // ── resolve_ref tests ─────────────────────────────────────────────── + + #[test] + fn test_resolve_ref_valid_branch_resolves_to_oid() { + let (_tmp, repo_path) = create_test_repo(); + + let result = resolve_ref(&repo_path, "main").unwrap(); + assert_eq!( + result.oid.len(), + 40, + "resolved OID should be 40 characters, got: {}", + result.oid + ); + assert!( + result.oid.chars().all(|c| c.is_ascii_hexdigit()), + "resolved OID should be valid hex, got: {}", + result.oid + ); + } + + #[test] + fn test_resolve_ref_invalid_ref_returns_error() { + let (_tmp, repo_path) = create_test_repo(); + + let result = resolve_ref(&repo_path, "nonexistent-branch"); + assert!( + result.is_err(), + "resolving a nonexistent ref should return an error" + ); + let err_msg = result.unwrap_err(); + assert!( + err_msg.contains("Failed to resolve"), + "error should mention resolution failure, got: {}", + err_msg + ); + } + + #[test] + fn test_resolve_ref_head_resolves_correctly() { + let (_tmp, repo_path) = create_test_repo(); + + // HEAD should resolve to the same OID as main (since HEAD points to main) + let head_result = resolve_ref(&repo_path, "HEAD").unwrap(); + let main_result = resolve_ref(&repo_path, "main").unwrap(); + + assert_eq!( + head_result.oid, main_result.oid, + "HEAD and main should resolve to the same OID" + ); + assert_eq!(head_result.oid.len(), 40); + assert!(head_result.oid.chars().all(|c| c.is_ascii_hexdigit())); + } } diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index 78cb321..caa95f1 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -1,6 +1,6 @@ +use std::sync::Mutex; #[cfg(debug_assertions)] use tauri::Manager; -use std::sync::Mutex; mod git; mod watcher; @@ -51,7 +51,11 @@ fn git_diff(path: String, base: String, compare: String) -> Result Result { +fn read_file_blob( + path: String, + ref_name: String, + file_path: String, +) -> Result { git::read_file_blob(&path, &ref_name, &file_path) } @@ -61,7 +65,10 @@ fn list_files(path: String, ref_name: String) -> Result Result { +fn list_files_with_oids( + path: String, + ref_name: String, +) -> Result { git::list_files_with_oids(&path, &ref_name) } diff --git a/apps/desktop/src-tauri/src/watcher.rs b/apps/desktop/src-tauri/src/watcher.rs index cfb3824..56b1e13 100644 --- a/apps/desktop/src-tauri/src/watcher.rs +++ b/apps/desktop/src-tauri/src/watcher.rs @@ -18,6 +18,46 @@ pub struct RefsChangedPayload { pub repo_path: String, } +/// Classification of a file system event path. +#[derive(Debug, PartialEq)] +pub enum EventKind { + /// Change to .git/refs or .git/HEAD — branch/tag metadata changed. + RefsChanged, + /// Change to a working directory file. Contains the normalized relative path. + WorkdirChanged(String), + /// Path should be ignored (node_modules, .git/objects, .git/logs, target, etc.). + Ignored, +} + +/// Classify a changed path relative to the repo root. +/// +/// Pure function extracted from the watcher callback for testability. +pub fn classify_path(path: &Path, repo_root: &Path, git_dir: &Path) -> EventKind { + // Check if path is under .git/refs or .git/HEAD + if path.starts_with(git_dir.join("refs")) || path.ends_with(".git/HEAD") { + return EventKind::RefsChanged; + } + + if path.starts_with(repo_root) { + let rel_path = path.strip_prefix(repo_root).unwrap(); + let path_str = rel_path.to_string_lossy(); + + if !path_str.starts_with(".git/objects") + && !path_str.starts_with(".git/logs") + && !path_str.contains("node_modules") + && !path_str.contains("/target/") + && !path_str.starts_with("target/") + { + let normalized = path_str.replace('\\', "/"); + if !normalized.starts_with(".git/") { + return EventKind::WorkdirChanged(normalized); + } + } + } + + EventKind::Ignored +} + pub struct RepoWatcher { debouncer: Debouncer, #[allow(dead_code)] // Used in closure passed to debouncer @@ -42,27 +82,14 @@ impl RepoWatcher { for event in events { for path in &event.paths { - // Check if path is under .git/refs or .git/HEAD - if path.starts_with(git_dir.join("refs")) - || path.ends_with(".git/HEAD") - { - refs_changed = true; - } else if path.starts_with(&repo_root) { - // Skip .git/objects, .git/logs, node_modules, target - let rel_path = path.strip_prefix(&repo_root).unwrap(); - let path_str = rel_path.to_string_lossy(); - - if !path_str.starts_with(".git/objects") - && !path_str.starts_with(".git/logs") - && !path_str.contains("node_modules") - && !path_str.contains("/target/") - && !path_str.starts_with("target/") - { - let normalized = path_str.replace('\\', "/"); - if !normalized.starts_with(".git/") { - workdir_files.push(normalized); - } + match classify_path(path, &repo_root, &git_dir) { + EventKind::RefsChanged => { + refs_changed = true; + } + EventKind::WorkdirChanged(normalized) => { + workdir_files.push(normalized); } + EventKind::Ignored => {} } } } @@ -112,3 +139,172 @@ impl RepoWatcher { drop(self.debouncer); } } + +#[cfg(test)] +mod tests { + use super::*; + use std::path::PathBuf; + + fn repo_root() -> PathBuf { + PathBuf::from("/repo") + } + + fn git_dir() -> PathBuf { + PathBuf::from("/repo/.git") + } + + // --- Ignored paths --- + + #[test] + fn ignores_node_modules() { + let path = PathBuf::from("/repo/node_modules/pkg/index.js"); + assert_eq!( + classify_path(&path, &repo_root(), &git_dir()), + EventKind::Ignored + ); + } + + #[test] + fn ignores_nested_node_modules() { + let path = PathBuf::from("/repo/packages/web/node_modules/pkg/lib.js"); + assert_eq!( + classify_path(&path, &repo_root(), &git_dir()), + EventKind::Ignored + ); + } + + #[test] + fn ignores_git_objects() { + let path = PathBuf::from("/repo/.git/objects/ab/cdef1234567890"); + assert_eq!( + classify_path(&path, &repo_root(), &git_dir()), + EventKind::Ignored + ); + } + + #[test] + fn ignores_git_logs() { + let path = PathBuf::from("/repo/.git/logs/HEAD"); + assert_eq!( + classify_path(&path, &repo_root(), &git_dir()), + EventKind::Ignored + ); + } + + #[test] + fn ignores_target_directory_at_root() { + let path = PathBuf::from("/repo/target/debug/build/libfoo.rlib"); + assert_eq!( + classify_path(&path, &repo_root(), &git_dir()), + EventKind::Ignored + ); + } + + #[test] + fn ignores_nested_target_directory() { + let path = PathBuf::from("/repo/crates/core/target/release/output"); + assert_eq!( + classify_path(&path, &repo_root(), &git_dir()), + EventKind::Ignored + ); + } + + #[test] + fn ignores_other_git_internal_paths() { + // .git/index, .git/config, etc. should be ignored (starts with .git/) + let path = PathBuf::from("/repo/.git/index"); + assert_eq!( + classify_path(&path, &repo_root(), &git_dir()), + EventKind::Ignored + ); + } + + // --- Regular source files (workdir-changed) --- + + #[test] + fn detects_regular_source_file() { + let path = PathBuf::from("/repo/src/main.rs"); + assert_eq!( + classify_path(&path, &repo_root(), &git_dir()), + EventKind::WorkdirChanged("src/main.rs".to_string()) + ); + } + + #[test] + fn detects_root_level_file() { + let path = PathBuf::from("/repo/Cargo.toml"); + assert_eq!( + classify_path(&path, &repo_root(), &git_dir()), + EventKind::WorkdirChanged("Cargo.toml".to_string()) + ); + } + + #[test] + fn detects_deeply_nested_file() { + let path = PathBuf::from("/repo/apps/web/src/components/Button.tsx"); + assert_eq!( + classify_path(&path, &repo_root(), &git_dir()), + EventKind::WorkdirChanged("apps/web/src/components/Button.tsx".to_string()) + ); + } + + // --- Refs-changed --- + + #[test] + fn detects_git_refs_heads_change() { + let path = PathBuf::from("/repo/.git/refs/heads/main"); + assert_eq!( + classify_path(&path, &repo_root(), &git_dir()), + EventKind::RefsChanged + ); + } + + #[test] + fn detects_git_refs_tags_change() { + let path = PathBuf::from("/repo/.git/refs/tags/v1.0.0"); + assert_eq!( + classify_path(&path, &repo_root(), &git_dir()), + EventKind::RefsChanged + ); + } + + #[test] + fn detects_git_refs_remotes_change() { + let path = PathBuf::from("/repo/.git/refs/remotes/origin/main"); + assert_eq!( + classify_path(&path, &repo_root(), &git_dir()), + EventKind::RefsChanged + ); + } + + #[test] + fn detects_git_head_change() { + let path = PathBuf::from("/repo/.git/HEAD"); + assert_eq!( + classify_path(&path, &repo_root(), &git_dir()), + EventKind::RefsChanged + ); + } + + // --- Edge cases --- + + #[test] + fn ignores_path_outside_repo() { + let path = PathBuf::from("/other/project/src/main.rs"); + assert_eq!( + classify_path(&path, &repo_root(), &git_dir()), + EventKind::Ignored + ); + } + + #[test] + fn file_named_target_not_ignored() { + // A file literally named "target" (not a directory) at root should pass + // because the filter checks "target/" prefix + let path = PathBuf::from("/repo/src/target.rs"); + assert_eq!( + classify_path(&path, &repo_root(), &git_dir()), + EventKind::WorkdirChanged("src/target.rs".to_string()) + ); + } +} diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json index 75ef9d2..209ad62 100644 --- a/apps/desktop/src-tauri/tauri.conf.json +++ b/apps/desktop/src-tauri/tauri.conf.json @@ -49,6 +49,7 @@ "dialog:allow-open", "fs:allow-read-dir", "fs:allow-read-file", + "clipboard-manager:allow-read-text", "clipboard-manager:allow-write-text" ] } diff --git a/apps/desktop/src/App.css b/apps/desktop/src/App.css index 67e4eb0..d356c6a 100644 --- a/apps/desktop/src/App.css +++ b/apps/desktop/src/App.css @@ -10,16 +10,16 @@ --surface-1: color-mix(in oklab, Canvas 96%, transparent); --surface-2: color-mix(in oklab, Canvas 92%, transparent); --border-col: color-mix(in hsl, currentColor 18%, transparent); - --ring: #646cff; - --primary: #646cff; + --ring: #22c55e; + --primary: #22c55e; --primary-contrast: CanvasText; } [data-theme="dark"] { --surface-1: color-mix(in oklab, Canvas 88%, transparent); --surface-2: color-mix(in oklab, Canvas 84%, transparent); --border-col: color-mix(in hsl, currentColor 26%, transparent); - --ring: #a3a6ff; - --primary: #a3a6ff; + --ring: #4ade80; + --primary: #22c55e; } :where(button,[role="button"],a,input,select,textarea):focus-visible { @@ -154,7 +154,9 @@ .gc-diff-bar { display: flex; align-items: center; + flex-wrap: wrap; gap: var(--space-4); + row-gap: var(--space-2); padding: var(--space-3) var(--space-4); border-bottom: 1px solid var(--border-col); background: var(--surface-2); @@ -162,12 +164,29 @@ z-index: 9; } +.diff-bar-workspace-controls { + display: flex; + align-items: flex-end; + gap: var(--space-2); + min-width: min(100%, 280px); + flex: 1 1 320px; +} + +.diff-bar-workspace-selector { + display: flex; + flex-direction: column; + gap: var(--space-1); + min-width: 220px; + flex: 1; +} + .diff-bar-branch-selector { display: flex; flex-direction: column; gap: var(--space-1); min-width: 200px; max-width: 300px; + flex: 1 1 220px; } .diff-bar-label { @@ -184,26 +203,6 @@ overflow: hidden; } -.diff-bar-project-info { - display: flex; - align-items: center; - gap: var(--space-2); - padding: var(--space-2) var(--space-3); - background: color-mix(in oklab, var(--surface-1) 50%, transparent); - border-radius: var(--radius-sm); - font-size: var(--text-sm); - color: inherit; - opacity: 0.8; -} - -.diff-bar-project-name { - font-weight: 500; - max-width: 200px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - .gc-main-content { display: grid; grid-template-columns: minmax(320px, 1fr) clamp(280px, 32vw, 460px); @@ -251,6 +250,10 @@ padding: var(--space-3); gap: var(--space-3); } + + .diff-bar-workspace-controls { + min-width: 100%; + } } /* Height-based media query for short desktop windows */ @@ -557,7 +560,7 @@ a.hint:active { } .landing-visual-button:focus-visible { - outline: 2px solid #646cff; + outline: 2px solid var(--ring); outline-offset: 2px; border-radius: var(--radius-lg); } diff --git a/apps/desktop/src/App.tsx b/apps/desktop/src/App.tsx index 4cd80ea..b4708c0 100644 --- a/apps/desktop/src/App.tsx +++ b/apps/desktop/src/App.tsx @@ -1,11 +1,12 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import './App.css' -import { ChevronsDown, ChevronsUp, CheckSquare, Square, Sun, Moon, Folder, FolderGit2, ListChecks, Copy, ArrowLeftRight } from 'lucide-react' +import { ChevronsDown, ChevronsUp, CheckSquare, Square, Sun, Moon, Folder, FolderGit2, ListChecks, Copy, ArrowLeftRight, Trash2 } from 'lucide-react' import { FileTreeView, PreviewModal, GitHubStarIconButton, BugIconButton } from '@gitcontext/ui' -import { type FileDiffStatus, isBinaryPath, MAX_CONCURRENT_READS } from '@gitcontext/core' -import { writeText } from '@tauri-apps/plugin-clipboard-manager' +import { type FileDiffStatus, MAX_CONCURRENT_READS } from '@gitcontext/core' +import { readText, writeText } from '@tauri-apps/plugin-clipboard-manager' import { useGitRepository } from './hooks/useGitRepository' import { useFileTree } from './hooks/useFileTree' +import { useTheme } from './hooks/useTheme' import { SelectedFilesPanel } from './components/SelectedFilesPanel' import { TopProgressBar } from './components/TopProgressBar' import { ErrorBanner } from './components/ErrorBanner' @@ -15,17 +16,73 @@ import { ContextFooter } from './components/ContextFooter' import { getModels } from './utils/models' import type { ModelInfo } from './types/models' import type { AppStatus } from './types/appStatus' -import { buildUnifiedDiffForStatus } from './utils/diff' +import { generateFileTreeText, buildHeader, buildFileSection } from './utils/copyOutput' import { countTokens } from './utils/tokenizer' import { TokenCountsProvider } from './context/TokenCountsContext' import { mapWithConcurrency } from './utils/concurrency' import { logError } from './utils/logger' import { debounce } from './utils/debounce' +import { + INVALID_CLIPBOARD_FORMAT_MESSAGE, + NO_MATCHING_FILES_MESSAGE, + parseClipboardPathLines, + resolveSelectablePaths, +} from './utils/clipboardBatchSelect' +import { + DEFAULT_WORKSPACE_SETTINGS, + findWorkspaceByPath, + getActiveSession, + getWorkspaceById, + getWorkspaceSelectionRestore, + listWorkspaceItems, + loadWorkspaceStore, + markWorkspaceOpened, + removeWorkspace, + saveWorkspaceStore, + setActiveWorkspace, + type WorkspaceSessionSnapshotInput, + type WorkspaceStore, + upsertWorkspace, + updateWorkspaceSession, +} from './utils/workspaceStore' + +type PendingWorkspaceSelectionRestore = { + id: number + source: 'workspace-open' | 'workspace-refresh' | 'workspace-autodetect' + workspaceId: string + workspaceName: string + workspacePath: string + baseBranch: string + compareBranch: string + selectedPaths: string[] + targetDiffSequence: number +} function AppContent() { const [, setAppStatus] = useState({ state: 'IDLE' }) const [errorMessage, setErrorMessage] = useState(null) + useEffect(() => { + const onRejection = (event: PromiseRejectionEvent) => { + event.preventDefault?.() + console.error('[unhandledrejection]', event.reason) + setErrorMessage( + event.reason instanceof Error ? event.reason.message : String(event.reason) + ) + } + const onError = (event: ErrorEvent) => { + console.error('[window.error]', event.error || event.message) + setErrorMessage(event.error?.message || event.message) + } + + window.addEventListener('unhandledrejection', onRejection) + window.addEventListener('error', onError) + return () => { + window.removeEventListener('unhandledrejection', onRejection) + window.removeEventListener('error', onError) + } + }, []) + const { currentDir, repoStatus, @@ -35,6 +92,7 @@ function AppContent() { setBaseBranch, compareBranch, setCompareBranch, + loadRepoFromHandle, selectNewRepo, refreshRepo, resetRepo, @@ -52,35 +110,7 @@ function AppContent() { } | null>(null) // Theme management - const [theme, setTheme] = useState<'light' | 'dark' | null>(() => { - try { - const saved = localStorage.getItem('gc.theme') - return saved === 'light' || saved === 'dark' ? saved : null - } catch (e) { - logError('themeLoad', e) - return null - } - }) - const [systemDark, setSystemDark] = useState(window.matchMedia('(prefers-color-scheme: dark)').matches) - useEffect(() => { - const mql = window.matchMedia('(prefers-color-scheme: dark)') - const handler = (e: MediaQueryListEvent) => setSystemDark(e.matches) - mql.addEventListener('change', handler) - return () => mql.removeEventListener('change', handler) - }, []) - const effectiveTheme = theme ?? (systemDark ? 'dark' : 'light') - useEffect(() => { - document.documentElement.setAttribute('data-theme', effectiveTheme) - }, [effectiveTheme]) - useEffect(() => { - try { - if (theme) localStorage.setItem('gc.theme', theme) - else localStorage.removeItem('gc.theme') - } catch (e) { - logError('themePersistence', e) - } - }, [theme]) - const toggleTheme = () => setTheme(effectiveTheme === 'dark' ? 'light' : 'dark') + const { effectiveTheme, toggleTheme } = useTheme() // User instructions const [userInstructions, setUserInstructions] = useState('') @@ -139,12 +169,14 @@ function AppContent() { // File tree management const { + isComputing: isDiffComputing, fileTree, statusByPath, showChangedOnly, setShowChangedOnly, expandedPaths, selectedPaths, + diffSequence, computeDiffAndTree, toggleExpand, toggleSelect, @@ -152,6 +184,9 @@ function AppContent() { collapseAll, selectAll, deselectAll, + addSelectedPaths, + removeSelectedPathsByPredicate, + setSelectedPathsDirect, revealPath, } = useFileTree(setAppStatus) @@ -175,6 +210,103 @@ function AppContent() { // Include binary files as file paths const [includeBinaryAsPaths, setIncludeBinaryAsPaths] = useState(false) + const initialWorkspaceStoreRef = useRef(null) + if (!initialWorkspaceStoreRef.current) { + initialWorkspaceStoreRef.current = loadWorkspaceStore() + } + const [workspaceStore, setWorkspaceStore] = useState(initialWorkspaceStoreRef.current) + const [selectedWorkspaceId, setSelectedWorkspaceId] = useState( + initialWorkspaceStoreRef.current.activeWorkspaceId ?? '', + ) + const workspaceStoreRef = useRef(workspaceStore) + useEffect(() => { + workspaceStoreRef.current = workspaceStore + }, [workspaceStore]) + const workspaceItems = useMemo(() => listWorkspaceItems(workspaceStore), [workspaceStore]) + const pendingSelectionRestoreCounterRef = useRef(0) + const workspaceSwitchRequestRef = useRef(0) + const skipNextAutoWorkspacePathRef = useRef(null) + const lastAutoWorkspaceSyncPathRef = useRef(null) + const [pendingSelectionRestore, setPendingSelectionRestore] = useState(null) + + const commitWorkspaceStore = useCallback((updater: (store: WorkspaceStore) => WorkspaceStore) => { + setWorkspaceStore((current) => { + const next = updater(current) + saveWorkspaceStore(next) + workspaceStoreRef.current = next + return next + }) + }, []) + + const setActiveWorkspaceSelection = useCallback((workspaceId: string | '') => { + setSelectedWorkspaceId(workspaceId) + commitWorkspaceStore((current) => setActiveWorkspace(current, workspaceId)) + }, [commitWorkspaceStore]) + + const buildWorkspaceSessionSnapshot = useCallback( + (overrides?: Partial): WorkspaceSessionSnapshotInput => ({ + sessionId: overrides?.sessionId, + sessionName: overrides?.sessionName, + baseBranch: overrides?.baseBranch ?? baseBranch, + compareBranch: overrides?.compareBranch ?? compareBranch, + selectedPaths: overrides?.selectedPaths ?? Array.from(selectedPaths), + settings: { + ...DEFAULT_WORKSPACE_SETTINGS, + selectedModel, + userInstructions, + includeFileTree, + includeBinaryAsPaths, + diffContextLines, + showChangedOnly, + activeTab, + ...(overrides?.settings ?? {}), + }, + }), + [ + activeTab, + baseBranch, + compareBranch, + diffContextLines, + includeBinaryAsPaths, + includeFileTree, + selectedModel, + selectedPaths, + showChangedOnly, + userInstructions, + ], + ) + + const applyWorkspaceSettings = useCallback((settings: Partial) => { + setSelectedModel(settings.selectedModel ?? '') + setUserInstructions(settings.userInstructions ?? '') + setIncludeFileTree(settings.includeFileTree ?? DEFAULT_WORKSPACE_SETTINGS.includeFileTree) + setIncludeBinaryAsPaths(settings.includeBinaryAsPaths ?? DEFAULT_WORKSPACE_SETTINGS.includeBinaryAsPaths) + const nextContextLines = settings.diffContextLines ?? DEFAULT_WORKSPACE_SETTINGS.diffContextLines + diffContextImmediateRef.current = nextContextLines + setDiffContextImmediate(nextContextLines) + setDiffContextLines(nextContextLines) + setShowChangedOnly(settings.showChangedOnly ?? DEFAULT_WORKSPACE_SETTINGS.showChangedOnly) + setActiveTab(settings.activeTab ?? DEFAULT_WORKSPACE_SETTINGS.activeTab) + }, [setShowChangedOnly]) + + const queueWorkspaceSelectionRestore = useCallback( + (params: Omit & { expectNextDiff?: boolean }) => { + const restoreId = ++pendingSelectionRestoreCounterRef.current + setPendingSelectionRestore({ + id: restoreId, + source: params.source, + workspaceId: params.workspaceId, + workspaceName: params.workspaceName, + workspacePath: params.workspacePath, + baseBranch: params.baseBranch, + compareBranch: params.compareBranch, + selectedPaths: params.selectedPaths, + targetDiffSequence: params.expectNextDiff === false ? diffSequence : diffSequence + 1, + }) + }, + [diffSequence], + ) + // Flip base and compare branches const flipBranches = useCallback(() => { if (baseBranch && compareBranch) { @@ -191,6 +323,324 @@ function AppContent() { } }, [repoStatus, baseBranch, compareBranch, gitClient, computeDiffAndTree, diffTrigger]) + // Keep selected workspace in sync when a repository is opened directly. + useEffect(() => { + if (!currentDir) { + setPendingSelectionRestore(null) + skipNextAutoWorkspacePathRef.current = null + lastAutoWorkspaceSyncPathRef.current = null + return + } + if (lastAutoWorkspaceSyncPathRef.current === currentDir) { + return + } + if (skipNextAutoWorkspacePathRef.current === currentDir) { + skipNextAutoWorkspacePathRef.current = null + lastAutoWorkspaceSyncPathRef.current = currentDir + return + } + lastAutoWorkspaceSyncPathRef.current = currentDir + const matchedWorkspace = findWorkspaceByPath(workspaceStoreRef.current, currentDir) + if (!matchedWorkspace) { + if (selectedWorkspaceId !== '') { + setActiveWorkspaceSelection('') + } + return + } + const activeSession = getActiveSession(matchedWorkspace) + setSelectedWorkspaceId(matchedWorkspace.id) + commitWorkspaceStore((current) => + markWorkspaceOpened(setActiveWorkspace(current, matchedWorkspace.id), matchedWorkspace.id), + ) + applyWorkspaceSettings(activeSession.settings) + if (activeSession.baseBranch && branches.includes(activeSession.baseBranch) && baseBranch !== activeSession.baseBranch) { + setBaseBranch(activeSession.baseBranch) + } + if ( + activeSession.compareBranch && + branches.includes(activeSession.compareBranch) && + compareBranch !== activeSession.compareBranch + ) { + setCompareBranch(activeSession.compareBranch) + } + queueWorkspaceSelectionRestore({ + source: 'workspace-autodetect', + workspaceId: matchedWorkspace.id, + workspaceName: matchedWorkspace.name, + workspacePath: matchedWorkspace.path, + baseBranch: activeSession.baseBranch, + compareBranch: activeSession.compareBranch, + selectedPaths: activeSession.selectedPaths, + expectNextDiff: true, + }) + }, [ + applyWorkspaceSettings, + baseBranch, + branches, + commitWorkspaceStore, + compareBranch, + currentDir, + queueWorkspaceSelectionRestore, + selectedWorkspaceId, + setActiveWorkspaceSelection, + setBaseBranch, + setCompareBranch, + ]) + + const handleSelectWorkspace = useCallback( + async (workspaceId: string | '') => { + if (workspaceId === '') { + setActiveWorkspaceSelection('') + return + } + const workspace = getWorkspaceById(workspaceStoreRef.current, workspaceId) + if (!workspace) { + setActiveWorkspaceSelection('') + return + } + const requestId = ++workspaceSwitchRequestRef.current + const session = getActiveSession(workspace) + skipNextAutoWorkspacePathRef.current = workspace.path + setErrorMessage(null) + const loaded = await loadRepoFromHandle(workspace.path, { + preferredBranches: { + base: session.baseBranch, + compare: session.compareBranch, + }, + }) + if (requestId !== workspaceSwitchRequestRef.current) return + if (!loaded) { + const shouldRemove = window.confirm( + 'This workspace path cannot be opened right now. Remove it from saved workspaces?', + ) + if (shouldRemove) { + commitWorkspaceStore((current) => removeWorkspace(current, workspaceId)) + setSelectedWorkspaceId('') + } + return + } + setSelectedWorkspaceId(workspaceId) + commitWorkspaceStore((current) => + markWorkspaceOpened(setActiveWorkspace(current, workspaceId), workspaceId), + ) + applyWorkspaceSettings(session.settings) + queueWorkspaceSelectionRestore({ + source: 'workspace-open', + workspaceId, + workspaceName: workspace.name, + workspacePath: workspace.path, + baseBranch: session.baseBranch, + compareBranch: session.compareBranch, + selectedPaths: session.selectedPaths, + expectNextDiff: true, + }) + }, + [ + applyWorkspaceSettings, + commitWorkspaceStore, + loadRepoFromHandle, + queueWorkspaceSelectionRestore, + setActiveWorkspaceSelection, + ], + ) + + const handleSaveWorkspace = useCallback(() => { + if (!currentDir) return + const selectedWorkspace = + selectedWorkspaceId !== '' ? getWorkspaceById(workspaceStoreRef.current, selectedWorkspaceId) : null + const byPathWorkspace = findWorkspaceByPath(workspaceStoreRef.current, currentDir) + const existing = selectedWorkspace ?? byPathWorkspace + const defaultName = + existing?.name || currentDir.split('/').filter(Boolean).pop() || 'Workspace' + let nextName = defaultName + try { + const enteredName = window.prompt('Enter a name for this workspace:', defaultName) + if (typeof enteredName === 'string') { + const trimmed = enteredName.trim() + if (!trimmed) return + nextName = trimmed + } + // `null` is treated as "prompt unavailable" fallback in desktop webviews. + } catch { + // Fall back to default name when prompt isn't supported by the runtime. + } + + const sessionId = existing ? getActiveSession(existing).id : undefined + const sessionName = existing ? getActiveSession(existing).name : undefined + const snapshot = buildWorkspaceSessionSnapshot({ sessionId, sessionName }) + let nextWorkspaceId = '' + commitWorkspaceStore((current) => { + const { store, workspaceId } = upsertWorkspace(current, { + name: nextName, + path: currentDir, + workspaceId: existing?.id, + snapshot, + markOpened: true, + }) + nextWorkspaceId = workspaceId + return store + }) + if (nextWorkspaceId) { + setSelectedWorkspaceId(nextWorkspaceId) + } + }, [buildWorkspaceSessionSnapshot, commitWorkspaceStore, currentDir, selectedWorkspaceId]) + + const handleDeleteWorkspace = useCallback(() => { + if (selectedWorkspaceId === '') return + const workspace = getWorkspaceById(workspaceStoreRef.current, selectedWorkspaceId) + if (!workspace) { + setActiveWorkspaceSelection('') + return + } + const confirmed = window.confirm(`Remove saved workspace "${workspace.name}"?`) + if (!confirmed) return + commitWorkspaceStore((current) => removeWorkspace(current, selectedWorkspaceId)) + setSelectedWorkspaceId('') + }, [commitWorkspaceStore, selectedWorkspaceId, setActiveWorkspaceSelection]) + + const handleSelectNewRepo = useCallback(async () => { + const selectedPath = await selectNewRepo() + if (!selectedPath) return + const matchedWorkspace = findWorkspaceByPath(workspaceStoreRef.current, selectedPath) + if (!matchedWorkspace) { + setActiveWorkspaceSelection('') + return + } + const activeSession = getActiveSession(matchedWorkspace) + setSelectedWorkspaceId(matchedWorkspace.id) + commitWorkspaceStore((current) => + markWorkspaceOpened(setActiveWorkspace(current, matchedWorkspace.id), matchedWorkspace.id), + ) + applyWorkspaceSettings(activeSession.settings) + if (activeSession.baseBranch && branches.includes(activeSession.baseBranch) && baseBranch !== activeSession.baseBranch) { + setBaseBranch(activeSession.baseBranch) + } + if ( + activeSession.compareBranch && + branches.includes(activeSession.compareBranch) && + compareBranch !== activeSession.compareBranch + ) { + setCompareBranch(activeSession.compareBranch) + } + queueWorkspaceSelectionRestore({ + source: 'workspace-autodetect', + workspaceId: matchedWorkspace.id, + workspaceName: matchedWorkspace.name, + workspacePath: matchedWorkspace.path, + baseBranch: activeSession.baseBranch, + compareBranch: activeSession.compareBranch, + selectedPaths: activeSession.selectedPaths, + expectNextDiff: true, + }) + }, [ + applyWorkspaceSettings, + baseBranch, + branches, + commitWorkspaceStore, + compareBranch, + queueWorkspaceSelectionRestore, + selectNewRepo, + setActiveWorkspaceSelection, + setBaseBranch, + setCompareBranch, + ]) + + const handleRefreshWorkspace = useCallback(async () => { + const baseSnapshot = baseBranch + const compareSnapshot = compareBranch + const currentSelection = Array.from(selectedPaths) + const currentWorkspace = + selectedWorkspaceId !== '' ? getWorkspaceById(workspaceStoreRef.current, selectedWorkspaceId) : null + if (currentWorkspace) { + queueWorkspaceSelectionRestore({ + source: 'workspace-refresh', + workspaceId: currentWorkspace.id, + workspaceName: currentWorkspace.name, + workspacePath: currentWorkspace.path, + baseBranch: baseSnapshot, + compareBranch: compareSnapshot, + selectedPaths: currentSelection, + expectNextDiff: true, + }) + } + await refreshRepo({ + preferredBranches: { + base: baseSnapshot, + compare: compareSnapshot, + }, + }) + }, [baseBranch, compareBranch, queueWorkspaceSelectionRestore, refreshRepo, selectedPaths, selectedWorkspaceId]) + + // Persist the latest session snapshot for the active saved workspace. + useEffect(() => { + if (!currentDir || selectedWorkspaceId === '') return + const timeout = window.setTimeout(() => { + const workspace = getWorkspaceById(workspaceStoreRef.current, selectedWorkspaceId) + if (!workspace) return + if (workspace.path !== currentDir) return + const activeSession = getActiveSession(workspace) + const snapshot = buildWorkspaceSessionSnapshot({ + sessionId: activeSession.id, + sessionName: activeSession.name, + }) + commitWorkspaceStore((current) => + updateWorkspaceSession(setActiveWorkspace(current, selectedWorkspaceId), selectedWorkspaceId, snapshot), + ) + }, 300) + return () => window.clearTimeout(timeout) + }, [buildWorkspaceSessionSnapshot, commitWorkspaceStore, currentDir, selectedWorkspaceId]) + + useEffect(() => { + if (!pendingSelectionRestore) return + if (!currentDir || repoStatus.state !== 'ready' || isDiffComputing) return + if (currentDir !== pendingSelectionRestore.workspacePath) return + if (diffSequence < pendingSelectionRestore.targetDiffSequence) return + + const { matched, missing } = getWorkspaceSelectionRestore( + pendingSelectionRestore.selectedPaths, + statusByPath.keys(), + ) + setSelectedPathsDirect(matched) + + const branchMismatch = + pendingSelectionRestore.baseBranch !== baseBranch || + pendingSelectionRestore.compareBranch !== compareBranch + if (missing.length > 0 || branchMismatch) { + const pieces: string[] = [] + if (branchMismatch) { + pieces.push( + `Restored against ${baseBranch} -> ${compareBranch} instead of ${pendingSelectionRestore.baseBranch} -> ${pendingSelectionRestore.compareBranch}.`, + ) + } + if (missing.length > 0) { + const preview = missing.slice(0, 5).join(', ') + pieces.push( + `Could not re-select ${missing.length} saved file${missing.length === 1 ? '' : 's'}: ${preview}${missing.length > 5 ? ', ...' : ''}`, + ) + } + setErrorMessage(pieces.join(' ')) + } else if (pendingSelectionRestore.source !== 'workspace-refresh') { + setErrorMessage(null) + } + setPendingSelectionRestore(null) + }, [ + baseBranch, + compareBranch, + currentDir, + diffSequence, + isDiffComputing, + pendingSelectionRestore, + repoStatus.state, + setSelectedPathsDirect, + statusByPath, + ]) + + useEffect(() => { + if (repoStatus.state === 'error') { + setPendingSelectionRestore(null) + } + }, [repoStatus.state]) + // Preview file const previewFile = useCallback(async (path: string, status: FileDiffStatus) => { if (!gitClient || !baseBranch || !compareBranch) return @@ -244,79 +694,26 @@ function AppContent() { // Generate file tree if requested (only for selected files) let fileTreeText = '' if (includeFileTree && fileTree && selectedPaths.size > 0) { - const lines: string[] = ['```', '📦 Repository Structure', ''] - - // Helper to check if a directory contains any selected files - const hasSelectedFiles = (node: any): boolean => { - if (node.type === 'file') { - return selectedPaths.has(node.path) - } - if (node.children) { - return node.children.some((child: any) => hasSelectedFiles(child)) - } - return false - } - - const walk = (node: any, depth: number) => { - // Only include directories that contain selected files, or files that are selected - if (node.type === 'file' && !selectedPaths.has(node.path)) { - return - } - if (node.type === 'dir' && !hasSelectedFiles(node)) { - return - } - - if (depth > 0) { - const indent = ' '.repeat(depth - 1) - const icon = node.type === 'dir' ? '📁' : '📄' - const status = node.status ? ` [${node.status.toUpperCase()}]` : '' - lines.push(`${indent}${icon} ${node.name}${status}`) - } - - if (node.children) { - for (const child of node.children) { - walk(child, depth + 1) - } - } - } - walk(fileTree, 0) - lines.push('```', '') - fileTreeText = lines.join('\n') + fileTreeText = generateFileTreeText(fileTree, selectedPaths) } // Build header - const header = [ - `# Git Diff Context: ${baseBranch} → ${compareBranch}`, - '', - `Repository: ${currentDir || 'Unknown'}`, - `Base: ${baseBranch}`, - `Compare: ${compareBranch}`, - `Files: ${paths.length}`, - '', - ] - - if (userInstructions.trim()) { - header.push('## Instructions', '', userInstructions.trim(), '') - } - - if (fileTreeText) { - header.push('## File Tree', '', fileTreeText) - } - - header.push('## Diffs', '') + const headerText = buildHeader({ + baseBranch, + compareBranch, + currentDir: currentDir || '', + fileCount: paths.length, + userInstructions, + fileTreeText, + }) - const output = [header.join('\n')] + const output = [headerText] // Fetch file contents with bounded concurrency const results = await mapWithConcurrency( paths, async (path) => { const status = statusByPath.get(path) ?? 'unchanged' - const looksBinary = isBinaryPath(path) - - if (looksBinary) { - return `## FILE: ${path} (${status.toUpperCase()})\n\n[Binary file]\n\n` - } const needBase = status !== 'add' const needCompare = status !== 'remove' @@ -325,21 +722,7 @@ function AppContent() { needCompare ? gitClient.readFile(compareBranch, path) : Promise.resolve(undefined), ]) - if (status === 'modify' || status === 'add' || status === 'remove') { - const isBinary = Boolean((baseRes as any)?.binary) || Boolean((compareRes as any)?.binary) - if (isBinary) { - return `## FILE: ${path} (${status.toUpperCase()})\n\n[Binary file]\n\n` - } - if (status === 'add' && ctx === Number.MAX_SAFE_INTEGER) { - return `## FILE: ${path} (ADD)\n\n\`\`\`\n${(compareRes as any)?.text ?? ''}\n\`\`\`\n\n` - } - const diffText = buildUnifiedDiffForStatus(status, path, baseRes as any, compareRes as any, { context: ctx }) - return diffText ? `## FILE: ${path} (${status.toUpperCase()})\n\n\`\`\`diff\n${diffText}\n\`\`\`\n\n` : '' - } else { - const isBinary = Boolean((baseRes as any)?.binary) - const text = isBinary || (baseRes as any)?.notFound ? '' : (baseRes as any)?.text ?? '' - return text ? `## FILE: ${path} (UNCHANGED)\n\n\`\`\`\n${text}\n\`\`\`\n\n` : '' - } + return buildFileSection(path, status, baseRes, compareRes, ctx) }, { limit: MAX_CONCURRENT_READS } ) @@ -356,6 +739,41 @@ function AppContent() { } }, [gitClient, baseBranch, compareBranch, selectedPaths, diffContextLines, statusByPath, userInstructions, fileTree, includeFileTree, showChangedOnly, currentDir]) + const handleBatchSelectFromClipboard = useCallback(async () => { + if (!currentDir || !fileTree) return + + try { + const clipboardText = await readText() + const lines = parseClipboardPathLines(clipboardText) + if (lines.length === 0) { + setErrorMessage(INVALID_CLIPBOARD_FORMAT_MESSAGE) + return + } + + const selectableSet = new Set(statusByPath.keys()) + const { matched, invalidCount, outsideRepoCount } = resolveSelectablePaths(lines, currentDir, selectableSet) + + if (matched.length === 0) { + if (invalidCount === lines.length && outsideRepoCount === 0) { + setErrorMessage(INVALID_CLIPBOARD_FORMAT_MESSAGE) + } else { + setErrorMessage(NO_MATCHING_FILES_MESSAGE) + } + return + } + + addSelectedPaths(matched) + setErrorMessage(null) + } catch (err) { + logError('batchSelectFromClipboard', err) + setErrorMessage('Failed to read clipboard content.') + } + }, [currentDir, fileTree, statusByPath, addSelectedPaths]) + + const handleRemoveTestPathsFromSelection = useCallback(() => { + removeSelectedPathsByPredicate((path) => path.toLowerCase().includes('test')) + }, [removeSelectedPathsByPredicate]) + // Calculate file tree tokens useEffect(() => { if (!includeFileTree || !fileTree || selectedPaths.size === 0) { @@ -459,7 +877,7 @@ function AppContent() { - @@ -479,7 +897,7 @@ function AppContent() { leaving your machine.

-
@@ -521,10 +939,14 @@ function AppContent() { onBaseBranchChange={setBaseBranch} onCompareBranchChange={setCompareBranch} onFlip={flipBranches} - onRefresh={refreshRepo} + onRefresh={handleRefreshWorkspace} disabled={isLoading} - projectName={currentDir ? currentDir.split('/').pop() || currentDir : undefined} - projectPath={currentDir || undefined} + workspaces={workspaceItems} + selectedWorkspaceId={selectedWorkspaceId} + currentWorkspacePath={currentDir || ''} + onWorkspaceSelect={handleSelectWorkspace} + onSaveWorkspace={handleSaveWorkspace} + onDeleteWorkspace={handleDeleteWorkspace} />
@@ -539,6 +961,8 @@ function AppContent() { + +