Skip to content

Latest commit

 

History

History
659 lines (583 loc) · 24.7 KB

File metadata and controls

659 lines (583 loc) · 24.7 KB

Lifecycle

简体中文

This guide applies to the publication-candidate artifact targeting 0.1.0-rc.1. Bundled text does not prove current publication or marketplace availability. Use host-managed lifecycle controls only for a trusted listing visible to the current account; otherwise use the receipt-checked preview procedure below.

Receipt-checked preview lifecycle

The preview installer writes .structured-thinking-installation into every installed Skill. The lifecycle script refuses to move a directory when its receipt is missing, is a symbolic link, or does not match the product, Skill name, version, source commit, source-tree digest, scope, and absolute discovery path. It never deletes the shared skills/ root or an unrelated Skill.

An uninstall moves all three verified directories together into .agents/.structured-thinking-backups/, outside the Skill discovery root. This is deliberately reversible. A restore requires the selected discovery root to be empty for all three names and accepts only a receipt-checked backup directly under that backup root.

Save the appropriate block outside every discovery and backup root. Run it as a script so an unhandled error produces a native non-zero process exit.

Bash lifecycle script

Save as /tmp/structured-thinking-preview-lifecycle.sh:

#!/usr/bin/env bash
set -Eeuo pipefail

EXPECTED_VERSION="0.1.0-rc.1"
SKILLS=(structured-thinking decision-analysis incident-analysis)
ACTION="${1:-}"
SCOPE="${2:-}"
REPO_INPUT="${3:-}"
BACKUP_INPUT="${4:-}"

die() {
  printf 'ERROR: %s\n' "$*" >&2
  exit 1
}

[[ "$ACTION" == "inspect" || "$ACTION" == "uninstall" || "$ACTION" == "restore" ||
   "$ACTION" == "purge" ]] ||
  die "usage: $0 inspect|uninstall user | $0 inspect|uninstall repository REPO_ROOT | $0 restore|purge user '' BACKUP_ROOT | $0 restore|purge repository REPO_ROOT BACKUP_ROOT"
[[ "$SCOPE" == "user" || "$SCOPE" == "repository" ]] || die "scope must be user or repository"

if [[ "$SCOPE" == "user" ]]; then
  AGENT_ROOT="$HOME/.agents"
else
  [[ -n "$REPO_INPUT" ]] || die "REPO_ROOT is required for repository scope"
  REPO_ROOT="$(cd "$REPO_INPUT" && pwd -P)"
  [[ "$REPO_ROOT" != *$'\n'* && "$REPO_ROOT" != *$'\r'* ]] ||
    die "REPO_ROOT must not contain CR or LF"
  AGENT_ROOT="$REPO_ROOT/.agents"
fi

[[ -d "$AGENT_ROOT" && ! -L "$AGENT_ROOT" ]] || die "invalid Agent root: $AGENT_ROOT"
AGENT_ROOT="$(cd "$AGENT_ROOT" && pwd -P)"
[[ "$AGENT_ROOT" != *$'\n'* && "$AGENT_ROOT" != *$'\r'* ]] ||
  die "Agent root must not contain CR or LF"
SKILL_ROOT="$AGENT_ROOT/skills"
[[ -d "$SKILL_ROOT" && ! -L "$SKILL_ROOT" ]] || die "invalid Skill root: $SKILL_ROOT"
SKILL_ROOT="$(cd "$SKILL_ROOT" && pwd -P)"
[[ "$SKILL_ROOT" != *$'\n'* && "$SKILL_ROOT" != *$'\r'* ]] ||
  die "Skill root must not contain CR or LF"
BACKUP_BASE="$AGENT_ROOT/.structured-thinking-backups"

prepare_backup_base() {
  if [[ -L "$BACKUP_BASE" ]]; then
    die "backup base must not be a symbolic link: $BACKUP_BASE"
  fi
  if [[ -e "$BACKUP_BASE" && ! -d "$BACKUP_BASE" ]]; then
    die "backup base is not a directory: $BACKUP_BASE"
  fi
  mkdir -p "$BACKUP_BASE"
  BACKUP_BASE="$(cd "$BACKUP_BASE" && pwd -P)"
  [[ "$(dirname "$BACKUP_BASE")" == "$AGENT_ROOT" ]] ||
    die "backup base escaped Agent root: $BACKUP_BASE"
  [[ "$BACKUP_BASE" != *$'\n'* && "$BACKUP_BASE" != *$'\r'* ]] ||
    die "backup base must not contain CR or LF"
}

valid_receipt() {
  local actual="$1"
  local skill="$2"
  local expected_path="$3"
  local receipt="$actual/.structured-thinking-installation"
  [[ -d "$actual" && ! -L "$actual" && -f "$receipt" && ! -L "$receipt" ]] &&
    grep -Fqx "receipt_format=1" "$receipt" &&
    grep -Fqx "project=structured-thinking" "$receipt" &&
    grep -Fqx "skill=$skill" "$receipt" &&
    grep -Fqx "version=$EXPECTED_VERSION" "$receipt" &&
    grep -Eq '^source_commit=[0-9a-fA-F]{40,64}$' "$receipt" &&
    grep -Eq '^source_tree=[0-9a-fA-F]{40,64}$' "$receipt" &&
    grep -Fqx "scope=$SCOPE" "$receipt" &&
    grep -Fqx "installed_path=$expected_path" "$receipt" &&
    grep -Eq '^installed_at=[0-9]{4}-[0-9]{2}-[0-9]{2}T' "$receipt"
}

if [[ "$ACTION" == "inspect" ]]; then
  for skill in "${SKILLS[@]}"; do
    destination="$SKILL_ROOT/$skill"
    valid_receipt "$destination" "$skill" "$destination" ||
      die "missing or mismatched receipt: $destination"
    printf '%s\n' "$destination"
    grep -E '^(version|source_commit|source_tree|scope|installed_path|installed_at)=' \
      "$destination/.structured-thinking-installation"
  done
  exit 0
fi

LOCK_DIR="$AGENT_ROOT/.structured-thinking-install.lock"
mkdir "$LOCK_DIR" 2>/dev/null || die "another install or lifecycle operation may be active: $LOCK_DIR"
MODE=""
ACTIVE_BACKUP=""
MOVED=()

cleanup() {
  local status=$?
  trap - EXIT
  if (( status != 0 )); then
    if [[ "$MODE" == "uninstall" ]]; then
      for skill in "${MOVED[@]}"; do
        source="$ACTIVE_BACKUP/$skill"
        destination="$SKILL_ROOT/$skill"
        if valid_receipt "$source" "$skill" "$destination" &&
           [[ ! -e "$destination" && ! -L "$destination" ]]; then
          mv -n "$source" "$destination"
          if [[ -e "$source" || -L "$source" ]]; then
            printf 'REFUSED late collision while restoring %s\n' "$skill" >&2
          fi
        else
          printf 'REFUSED automatic restore for %s\n' "$skill" >&2
        fi
      done
    elif [[ "$MODE" == "restore" ]]; then
      mkdir -p "$ACTIVE_BACKUP"
      for skill in "${MOVED[@]}"; do
        source="$SKILL_ROOT/$skill"
        destination="$ACTIVE_BACKUP/$skill"
        if valid_receipt "$source" "$skill" "$SKILL_ROOT/$skill" &&
           [[ ! -e "$destination" && ! -L "$destination" ]]; then
          mv -n "$source" "$destination"
          if [[ -e "$source" || -L "$source" ]]; then
            printf 'REFUSED late collision while repairing backup %s\n' "$skill" >&2
          fi
        else
          printf 'REFUSED automatic backup repair for %s\n' "$skill" >&2
        fi
      done
    fi
  fi
  if [[ -n "$ACTIVE_BACKUP" && -d "$ACTIVE_BACKUP" && ! -L "$ACTIVE_BACKUP" ]]; then
    rmdir "$ACTIVE_BACKUP" 2>/dev/null || true
  fi
  rmdir "$LOCK_DIR" 2>/dev/null || true
  exit "$status"
}
trap cleanup EXIT

if [[ "$ACTION" == "uninstall" ]]; then
  for skill in "${SKILLS[@]}"; do
    destination="$SKILL_ROOT/$skill"
    valid_receipt "$destination" "$skill" "$destination" ||
      die "missing or mismatched receipt: $destination"
  done
  prepare_backup_base
  ACTIVE_BACKUP="$(mktemp -d "$BACKUP_BASE/backup.XXXXXX")"
  MODE="uninstall"
  for skill in "${SKILLS[@]}"; do
    mv -n "$SKILL_ROOT/$skill" "$ACTIVE_BACKUP/$skill"
    [[ ! -e "$SKILL_ROOT/$skill" && ! -L "$SKILL_ROOT/$skill" ]] ||
      die "no-clobber move refused backup destination: $ACTIVE_BACKUP/$skill"
    MOVED+=("$skill")
  done
  MODE=""
  printf '%s\n' "$ACTIVE_BACKUP"
  ACTIVE_BACKUP=""
  exit 0
fi

[[ -n "$BACKUP_INPUT" ]] || die "BACKUP_ROOT is required for restore or purge"
prepare_backup_base
[[ -d "$BACKUP_INPUT" && ! -L "$BACKUP_INPUT" ]] || die "invalid backup root: $BACKUP_INPUT"
ACTIVE_BACKUP="$(cd "$BACKUP_INPUT" && pwd -P)"
[[ "$(dirname "$ACTIVE_BACKUP")" == "$BACKUP_BASE" ]] ||
  die "backup must be a direct child of $BACKUP_BASE"
for skill in "${SKILLS[@]}"; do
  destination="$SKILL_ROOT/$skill"
  valid_receipt "$ACTIVE_BACKUP/$skill" "$skill" "$destination" ||
    die "missing or mismatched backup receipt: $ACTIVE_BACKUP/$skill"
done
if [[ "$ACTION" == "purge" ]]; then
  entry_count="$(find "$ACTIVE_BACKUP" -mindepth 1 -maxdepth 1 -print | wc -l | tr -d '[:space:]')"
  [[ "$entry_count" == "${#SKILLS[@]}" ]] ||
    die "backup contains unexpected entries: $ACTIVE_BACKUP"
  for skill in "${SKILLS[@]}"; do
    rm -rf -- "$ACTIVE_BACKUP/$skill"
  done
  rmdir "$ACTIVE_BACKUP"
  printf 'Purged receipt-checked backup %s\n' "$ACTIVE_BACKUP"
  ACTIVE_BACKUP=""
  exit 0
fi
for skill in "${SKILLS[@]}"; do
  destination="$SKILL_ROOT/$skill"
  if [[ -e "$destination" || -L "$destination" ]]; then
    die "restore destination is not empty: $destination"
  fi
done
MODE="restore"
for skill in "${SKILLS[@]}"; do
  mv -n "$ACTIVE_BACKUP/$skill" "$SKILL_ROOT/$skill"
  [[ ! -e "$ACTIVE_BACKUP/$skill" && ! -L "$ACTIVE_BACKUP/$skill" ]] ||
    die "no-clobber move refused late collision: $SKILL_ROOT/$skill"
  MOVED+=("$skill")
done
rmdir "$ACTIVE_BACKUP"
MODE=""
ACTIVE_BACKUP=""
printf 'Restored %s into %s\n' "$EXPECTED_VERSION" "$SKILL_ROOT"

PowerShell lifecycle script

Save as $env:TEMP/structured-thinking-preview-lifecycle.ps1:

param(
  [Parameter(Mandatory = $true)]
  [ValidateSet("inspect", "uninstall", "restore", "purge")]
  [string]$Action,
  [Parameter(Mandatory = $true)]
  [ValidateSet("user", "repository")]
  [string]$Scope,
  [string]$RepoRoot,
  [string]$BackupRoot
)

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$expectedVersion = "0.1.0-rc.1"
$skills = @("structured-thinking", "decision-analysis", "incident-analysis")
$moved = [System.Collections.Generic.List[string]]::new()
$mode = ""
$activeBackup = $null
$lockDir = $null

function Assert-NoLineBreak([string]$Name, [string]$Value) {
  if ($Value.Contains("`r") -or $Value.Contains("`n")) { throw "$Name must not contain CR or LF" }
}

function Get-ExistingEntry([string]$Path) {
  Get-Item -LiteralPath $Path -Force -ErrorAction SilentlyContinue
}

if ($Scope -eq "user") {
  $agentRoot = Join-Path $HOME ".agents"
} else {
  if ([string]::IsNullOrWhiteSpace($RepoRoot)) { throw "RepoRoot is required for repository scope" }
  $resolvedRepoRoot = (Resolve-Path -LiteralPath $RepoRoot).Path
  Assert-NoLineBreak "RepoRoot" $resolvedRepoRoot
  $agentRoot = Join-Path $resolvedRepoRoot ".agents"
}
$agentEntry = Get-ExistingEntry $agentRoot
if ($null -eq $agentEntry -or -not $agentEntry.PSIsContainer -or
    ($agentEntry.Attributes -band [IO.FileAttributes]::ReparsePoint)) {
  throw "Invalid Agent root: $agentRoot"
}
$agentRoot = (Resolve-Path -LiteralPath $agentRoot).Path
Assert-NoLineBreak "Agent root" $agentRoot
$skillRoot = Join-Path $agentRoot "skills"
$skillEntry = Get-ExistingEntry $skillRoot
if ($null -eq $skillEntry -or -not $skillEntry.PSIsContainer -or
    ($skillEntry.Attributes -band [IO.FileAttributes]::ReparsePoint)) {
  throw "Invalid Skill root: $skillRoot"
}
$skillRoot = (Resolve-Path -LiteralPath $skillRoot).Path
Assert-NoLineBreak "Skill root" $skillRoot
$backupBase = Join-Path $agentRoot ".structured-thinking-backups"

function Resolve-BackupBase {
  $entry = Get-ExistingEntry $backupBase
  if ($null -ne $entry -and
      (($entry.Attributes -band [IO.FileAttributes]::ReparsePoint) -or -not $entry.PSIsContainer)) {
    throw "Backup base must be a real directory: $backupBase"
  }
  New-Item -ItemType Directory -Force -Path $backupBase | Out-Null
  $resolved = (Resolve-Path -LiteralPath $backupBase).Path
  Assert-NoLineBreak "Backup base" $resolved
  if ((Split-Path $resolved -Parent) -ne $agentRoot) {
    throw "Backup base escaped Agent root: $resolved"
  }
  $resolved
}

function Test-Receipt([string]$Actual, [string]$Skill, [string]$ExpectedPath) {
  $entry = Get-ExistingEntry $Actual
  if ($null -eq $entry -or -not $entry.PSIsContainer -or
      ($entry.Attributes -band [IO.FileAttributes]::ReparsePoint)) { return $false }
  $receipt = Join-Path $Actual ".structured-thinking-installation"
  $receiptEntry = Get-ExistingEntry $receipt
  if ($null -eq $receiptEntry -or $receiptEntry.PSIsContainer -or
      ($receiptEntry.Attributes -band [IO.FileAttributes]::ReparsePoint)) { return $false }
  $lines = @(Get-Content -LiteralPath $receipt)
  foreach ($expected in @(
    "receipt_format=1",
    "project=structured-thinking",
    "skill=$Skill",
    "version=$expectedVersion",
    "scope=$Scope",
    "installed_path=$ExpectedPath"
  )) {
    if ($lines -cnotcontains $expected) { return $false }
  }
  $commit = $lines | Where-Object { $_ -cmatch '^source_commit=[0-9a-fA-F]{40,64}$' }
  $tree = $lines | Where-Object { $_ -cmatch '^source_tree=[0-9a-fA-F]{40,64}$' }
  $time = $lines | Where-Object { $_ -cmatch '^installed_at=[0-9]{4}-[0-9]{2}-[0-9]{2}T' }
  ($null -ne $commit) -and ($null -ne $tree) -and ($null -ne $time)
}

if ($Action -eq "inspect") {
  foreach ($skill in $skills) {
    $destination = [IO.Path]::GetFullPath((Join-Path $skillRoot $skill))
    if (-not (Test-Receipt $destination $skill $destination)) {
      throw "Missing or mismatched receipt: $destination"
    }
    Write-Output $destination
    Get-Content -LiteralPath (Join-Path $destination ".structured-thinking-installation") |
      Where-Object { $_ -match '^(version|source_commit|source_tree|scope|installed_path|installed_at)=' }
  }
  exit 0
}

$lockDir = Join-Path $agentRoot ".structured-thinking-install.lock"
if ($null -ne (Get-ExistingEntry $lockDir)) {
  throw "Another install or lifecycle operation may be active: $lockDir"
}
New-Item -ItemType Directory -Path $lockDir | Out-Null

try {
  if ($Action -eq "uninstall") {
    foreach ($skill in $skills) {
      $destination = [IO.Path]::GetFullPath((Join-Path $skillRoot $skill))
      if (-not (Test-Receipt $destination $skill $destination)) {
        throw "Missing or mismatched receipt: $destination"
      }
    }
    $backupBase = Resolve-BackupBase
    $activeBackup = Join-Path $backupBase ("backup." + [guid]::NewGuid())
    New-Item -ItemType Directory -Path $activeBackup | Out-Null
    $mode = "uninstall"
    foreach ($skill in $skills) {
      [IO.Directory]::Move((Join-Path $skillRoot $skill), (Join-Path $activeBackup $skill))
      $moved.Add($skill)
    }
    $mode = ""
    Write-Output $activeBackup
    $activeBackup = $null
  } else {
    if ([string]::IsNullOrWhiteSpace($BackupRoot)) { throw "BackupRoot is required for restore or purge" }
    $backupBase = Resolve-BackupBase
    $backupEntry = Get-ExistingEntry $BackupRoot
    if ($null -eq $backupEntry -or -not $backupEntry.PSIsContainer -or
        ($backupEntry.Attributes -band [IO.FileAttributes]::ReparsePoint)) {
      throw "Invalid backup root: $BackupRoot"
    }
    $activeBackup = (Resolve-Path -LiteralPath $BackupRoot).Path
    if ((Split-Path $activeBackup -Parent) -ne $backupBase) {
      throw "Backup must be a direct child of $backupBase"
    }
    foreach ($skill in $skills) {
      $destination = [IO.Path]::GetFullPath((Join-Path $skillRoot $skill))
      if (-not (Test-Receipt (Join-Path $activeBackup $skill) $skill $destination)) {
        throw "Missing or mismatched backup receipt: $activeBackup/$skill"
      }
    }
    if ($Action -eq "purge") {
      $entries = @(Get-ChildItem -LiteralPath $activeBackup -Force)
      if ($entries.Count -ne $skills.Count) {
        throw "Backup contains unexpected entries: $activeBackup"
      }
      foreach ($skill in $skills) {
        Remove-Item -Recurse -Force -LiteralPath (Join-Path $activeBackup $skill)
      }
      Remove-Item -LiteralPath $activeBackup
      Write-Host "Purged receipt-checked backup $activeBackup"
      $activeBackup = $null
      return
    }
    foreach ($skill in $skills) {
      $destination = [IO.Path]::GetFullPath((Join-Path $skillRoot $skill))
      if ($null -ne (Get-ExistingEntry $destination)) {
        throw "Restore destination is not empty: $destination"
      }
    }
    $mode = "restore"
    foreach ($skill in $skills) {
      [IO.Directory]::Move((Join-Path $activeBackup $skill), (Join-Path $skillRoot $skill))
      $moved.Add($skill)
    }
    Remove-Item -LiteralPath $activeBackup
    $mode = ""
    $activeBackup = $null
    Write-Host "Restored $expectedVersion into $skillRoot"
  }
} catch {
  if ($mode -eq "uninstall") {
    foreach ($skill in $moved) {
      $source = Join-Path $activeBackup $skill
      $destination = [IO.Path]::GetFullPath((Join-Path $skillRoot $skill))
      if ((Test-Receipt $source $skill $destination) -and
          ($null -eq (Get-ExistingEntry $destination))) {
        [IO.Directory]::Move($source, $destination)
      } else {
        Write-Error "REFUSED automatic restore for $skill" -ErrorAction Continue
      }
    }
  } elseif ($mode -eq "restore") {
    New-Item -ItemType Directory -Force -Path $activeBackup | Out-Null
    foreach ($skill in $moved) {
      $source = Join-Path $skillRoot $skill
      $destination = Join-Path $activeBackup $skill
      if ((Test-Receipt $source $skill ([IO.Path]::GetFullPath($source))) -and
          ($null -eq (Get-ExistingEntry $destination))) {
        [IO.Directory]::Move($source, $destination)
      } else {
        Write-Error "REFUSED automatic backup repair for $skill" -ErrorAction Continue
      }
    }
  }
  throw
} finally {
  if ($null -ne $activeBackup -and (Test-Path -LiteralPath $activeBackup -PathType Container)) {
    try { Remove-Item -LiteralPath $activeBackup -ErrorAction Stop } catch { }
  }
  if ($null -ne $lockDir -and (Test-Path -LiteralPath $lockDir -PathType Container)) {
    Remove-Item -LiteralPath $lockDir
  }
}

Inspect the active preview

Set the repository root explicitly for repository scope.

Bash

(
  set -euo pipefail
  bash /tmp/structured-thinking-preview-lifecycle.sh inspect user
  REPO_ROOT="/absolute/path/to/target-repository"
  bash /tmp/structured-thinking-preview-lifecycle.sh inspect repository "$REPO_ROOT"
)

PowerShell

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
pwsh -NoProfile -File "$env:TEMP/structured-thinking-preview-lifecycle.ps1" -Action inspect -Scope user
if ($LASTEXITCODE -ne 0) { throw "preview inspection failed" }
$REPO_ROOT = "C:\absolute\path\to\target-repository"
pwsh -NoProfile -File "$env:TEMP/structured-thinking-preview-lifecycle.ps1" `
  -Action inspect -Scope repository -RepoRoot $REPO_ROOT
if ($LASTEXITCODE -ne 0) { throw "preview inspection failed" }

Upgrade a manual preview

Keep the old and new version's installer and lifecycle scripts under different, version-labelled filenames. A script validates only the version embedded in its own artifact; overwriting the old script would remove the safe rollback path.

  1. Clone the new source into a separate source directory. Save its installer and lifecycle script separately; the installer will require its skills/ tree to be clean and committed.
  2. Close sessions that loaded the current preview.
  3. Use the old version's lifecycle script to run uninstall below, and record the backup path printed on standard output.
  4. Run the new version's canonical installer in Installation against the new source and the same explicit scope.
  5. Start a fresh session and run all Quick Start smoke checks.
  6. If installation or smoke checks fail, use the new version's lifecycle script to uninstall the new copy, then use the old version's script to restore the recorded old backup. Never merge old and new directories.

The backup and installer staging directories are siblings of skills/, never children of the discovery root.

Uninstall a manual preview

The command first validates all three receipts and then moves all three directories out of Skill discovery. It preserves every unrelated Skill and leaves the shared root intact. Retain the printed backup path until the uninstall or upgrade has been accepted.

Bash

(
  set -euo pipefail
  BACKUP_ROOT="$(bash /tmp/structured-thinking-preview-lifecycle.sh uninstall user)"
  printf 'rollback backup: %s\n' "$BACKUP_ROOT"
  # Repository scope:
  # REPO_ROOT="/absolute/path/to/target-repository"
  # BACKUP_ROOT="$(bash /tmp/structured-thinking-preview-lifecycle.sh uninstall repository "$REPO_ROOT")"
)

PowerShell

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$BACKUP_ROOT = pwsh -NoProfile -File "$env:TEMP/structured-thinking-preview-lifecycle.ps1" `
  -Action uninstall -Scope user
if ($LASTEXITCODE -ne 0) { throw "preview uninstall failed" }
Write-Host "rollback backup: $BACKUP_ROOT"
# Repository scope:
# $REPO_ROOT = "C:\absolute\path\to\target-repository"
# $BACKUP_ROOT = pwsh -NoProfile -File "$env:TEMP/structured-thinking-preview-lifecycle.ps1" `
#   -Action uninstall -Scope repository -RepoRoot $REPO_ROOT

Start a fresh session and confirm the three names are absent. The backup is not a discovery root and must not be added to one.

Roll back a manual preview

Restore requires all three selected destinations to be absent. If a newer preview is present, receipt-check and uninstall it first, retaining its separate backup.

Bash

(
  set -euo pipefail
  BACKUP_ROOT="/absolute/path/printed/by/uninstall"
  bash /tmp/structured-thinking-preview-lifecycle.sh restore user "" "$BACKUP_ROOT"
  # Repository scope:
  # REPO_ROOT="/absolute/path/to/target-repository"
  # bash /tmp/structured-thinking-preview-lifecycle.sh restore repository "$REPO_ROOT" "$BACKUP_ROOT"
)

PowerShell

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$BACKUP_ROOT = "C:\absolute\path\printed\by\uninstall"
pwsh -NoProfile -File "$env:TEMP/structured-thinking-preview-lifecycle.ps1" `
  -Action restore -Scope user -BackupRoot $BACKUP_ROOT
if ($LASTEXITCODE -ne 0) { throw "preview restore failed" }
# Repository scope:
# $REPO_ROOT = "C:\absolute\path\to\target-repository"
# pwsh -NoProfile -File "$env:TEMP/structured-thinking-preview-lifecycle.ps1" `
#   -Action restore -Scope repository -RepoRoot $REPO_ROOT -BackupRoot $BACKUP_ROOT

Start a fresh session and repeat the smoke checks after restoration.

Permanently remove an accepted backup

purge is irreversible. Use the same version-labelled lifecycle script that created the backup. It accepts only a direct child of the controlled backup base, validates all three receipts, and refuses a backup with missing or extra top-level entries.

Bash

(
  set -euo pipefail
  BACKUP_ROOT="/absolute/path/printed/by/uninstall"
  bash /tmp/structured-thinking-preview-lifecycle.sh purge user "" "$BACKUP_ROOT"
  # Repository scope:
  # REPO_ROOT="/absolute/path/to/target-repository"
  # bash /tmp/structured-thinking-preview-lifecycle.sh purge repository "$REPO_ROOT" "$BACKUP_ROOT"
)

PowerShell

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$BACKUP_ROOT = "C:\absolute\path\printed\by\uninstall"
pwsh -NoProfile -File "$env:TEMP/structured-thinking-preview-lifecycle.ps1" `
  -Action purge -Scope user -BackupRoot $BACKUP_ROOT
if ($LASTEXITCODE -ne 0) { throw "preview purge failed" }
# Repository scope:
# $REPO_ROOT = "C:\absolute\path\to\target-repository"
# pwsh -NoProfile -File "$env:TEMP/structured-thinking-preview-lifecycle.ps1" `
#   -Action purge -Scope repository -RepoRoot $REPO_ROOT -BackupRoot $BACKUP_ROOT

Interrupted lifecycle recovery

A force-killed process may leave .structured-thinking-install.lock, a backup, or a partially moved set. Do not remove the lock or move a directory merely to make the next command run. Confirm that no installer or lifecycle process is active; inspect all three discovery destinations, the candidate backup, and every receipt. A backup must remain directly under the documented backup base, and its receipts must still name the original absolute destinations. If ownership or the three-directory state is inconsistent, stop and preserve all artifacts for manual review. Remove an identified stale lock only after the state is understood; then use restore or restart the complete transaction instead of merging directories.

Host-managed Plugin lifecycle

Only use /plugins, Settings → Plugins, or another host-managed lifecycle control when the trusted listing is visible. Confirm the expected publisher and version before enable, update, disable, uninstall, or reinstall. Do not manually edit host-managed Plugin files and do not use a downloaded archive as a direct Plugin install. If an earlier trusted version is unavailable through the host, disable the affected Plugin and follow authenticated release or revocation guidance.

Verify a released asset before extraction

This verifies a future release archive for inspection; it does not install that archive as a Plugin. Set the published version explicitly and obtain the archive and checksum from the same authenticated release page.

Linux

(
  set -euo pipefail
  VERSION="0.1.0-rc.1"
  sha256sum --check "structured-thinking-${VERSION}.tar.gz.sha256"
)

macOS

(
  set -euo pipefail
  VERSION="0.1.0-rc.1"
  shasum -a 256 -c "structured-thinking-${VERSION}.tar.gz.sha256"
)

PowerShell

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$VERSION = "0.1.0-rc.1"
$archive = "structured-thinking-$VERSION.tar.gz"
$expected = ((Get-Content "$archive.sha256" -Raw).Trim() -split '\s+')[0].ToLowerInvariant()
$actual = (Get-FileHash -Algorithm SHA256 $archive).Hash.ToLowerInvariant()
if ($actual -ne $expected) { throw "Checksum mismatch; stop installation" }

A mismatch stops extraction. Checksum equality detects corruption or substitution relative to the checksum file; it does not authenticate the publisher or release page.