diff --git a/.agents/skills/freview/SKILL.md b/.agents/skills/freview/SKILL.md new file mode 100644 index 00000000..2876e0d5 --- /dev/null +++ b/.agents/skills/freview/SKILL.md @@ -0,0 +1,48 @@ +--- +name: freview +description: Read-only full-repository review. Audit Git branch ancestry and worktrees, GitHub issues and PRs against actual implementations, exact-commit CI, release gates, community files, dependency graph, history hygiene, and falsely completed or untouched work. +--- + +# freview — Full Repository Review + +This skill performs an evidence-first **read-only** audit. Never infer completion from a checkbox, label, commit subject, green test, PR description or an agent report alone. Never silently edit, fetch, merge, checkout, rebase, prune, delete, push, or update issues/PRs. Preserve active agents, branch ownership, uncommitted work and protected releases. + +## Procedure + +1. Inspect `AGENTS.md`, `CONTRIBUTING.md`, `ROADMAP.md`, the applicable release/QA ledger and branch policy **at the target ref**. Identify owner/repo, current UTC time, default branch, normal development base, candidate release branches, tag/release lineage and what requires physical validation. Do not confuse GitHub default branch with active development branch. +2. Record exact SHAs, upstreams and observation times. Detect local availability and active worktrees. **If you have only GitHub access, say so**; never invent observations about a user's Mac worktree or uncommitted local edits. +3. With local execution, run `node scripts/dev/freview.mjs --base origin/dev` (or the actual verified base; `--json` for machine-readable facts). It is a no-network, no-dependency, read-only snapshot. Do not fetch automatically; missing ref means partial coverage. +4. Obtain all pages of open issues, open PRs, recently closed issues, branches, tags, releases and relevant check runs. Record API/search pagination limits, permissions and unavailable resources. For each prioritized item, inspect bodies, material comments, review threads, current head SHA, base SHA, merge status, linked issues and exact-SHA CI. Do not silently equate the first twenty results with full inventory. +5. Compare **stable/released**, **candidate/integration**, and **isolated/in-progress** source views, each with exact refs and SHA. Local changes matter only if actually accessible. Work from high-risk contradictions first; read targeted source, tests and diffs rather than indiscriminate files. +6. Reconcile issue acceptance criteria one item at a time: expected user behavior → parser/route → implementation owner → service/protocol/persistence → rendering/configuration → test → exact-head CI → human QA → release reachability. Mark missing evidence, negative/edge cases, backwards compatibility and feature flags. +7. Run bounded hygiene checks: dirty and untracked state, branch graph and ancestry, PR target/drift, protected refs, CI skipped/failed gates, dependency lock alignment, community docs and accessibility, security reporting, cleanup artifacts, documentation accuracy, release/tag/changelog consistency. For complete guidance use [review matrix](references/review-matrix.md). +8. Produce the report and **stop**. Do not make fixes or change GitHub status without a separate explicit user instruction. + +## Status vocabulary — never use only Done + +- **Proposed/research only**: there is no verified accepted runtime implementation. +- **Untouched (scoped)**: searched relevant refs/source and found no implementation; record the inspected scope, not universal absence. +- **Partially implemented**: some acceptance behavior exists, other items are missing or blocked. +- **Implemented in isolated branch**: committed code exists, not integrated. +- **Integrated candidate**: included in an unreleased target branch; not automatically shipped. +- **Automated verified**: named tests/jobs at exact SHA, with OS and passes/failures/skips. A failing required CI shard means aggregate CI is **red**, even when suspected flaky. +- **Physical QA passed**: actually performed by the maintainer on the named host, not inferred from automated PTYs or SVG previews. +- **Released**: reachable commit in published tag/GitHub release with actual date. +- **Deliberately deferred**: documented decision not to ship or implement yet. +- **Unknown/unverifiable**: missing source, tests or permissions explicitly listed. + +A closed research ticket does not certify runtime functionality. Unchecked boxes are review leads, **not proof of missing code**. Open issue descriptions can be stale relative to newer PRs. Git ancestry matters, but squashed/cherry-picked work can require patch/tree inspection rather than simple `merge-base` conclusions. + +## Evidence and risk controls + +For each high-priority claim link issue → PR → commit → reachable branch → source path/behavior → regression test → exact-SHA CI → physical QA → release/tag. State direct observation separately from an agent's report. Verify the actual invocation/wiring: a standalone adapter, unused module or feature behind a disabled flag is not publicly usable. + +Review branches using `git merge-base --is-ancestor`, `git rev-list --left-right --count`, `git worktree list --porcelain`, `git log --graph` and exact diff paths. Classify safe *candidates* for later cleanup, never delete them automatically. A merged branch can still be an active worktree or needed audit record. + +Respect local T7 Shield policies for controlled scratch/build/test artifacts; verify physical mount and fail closed. Do not run comprehensive `npm ci`, release verification, security scanning, Git integrity traversal or expensive benchmarking merely for a review. Never expose secrets, full private environment dumps or command histories. Use read-only APIs; don't post comments or edit GitHub state. + +GitHub's **Network** graph concerns forks/branches; its **Dependency graph** concerns third-party package manifests, not source imports. Neither confirms feature completeness. + +## Report contract + +Dated and source-linked: repository/ref coverage and limitations; P0/P1/P2 concrete findings; acceptance and implementation matrix; untouched/partial/isolated/integrated/released distinctions; branch/PR ancestry and hygiene; CI, security and physical QA gates; community/accessibility and dependency graph; misleading closed/open issue statuses; minimal non-destructive next actions; **Not tested / Not observable** section. Never declare the repository entirely clean with incomplete coverage. diff --git a/.agents/skills/freview/references/review-matrix.md b/.agents/skills/freview/references/review-matrix.md new file mode 100644 index 00000000..d8025570 --- /dev/null +++ b/.agents/skills/freview/references/review-matrix.md @@ -0,0 +1,55 @@ +# freview — review matrix and audit decisions + +Use targeted levels. Never run cleanup or expensive verification solely to gather hygiene facts. + +## Evidence tiers + +- **Tier 0, fast/read-only:** `node scripts/dev/freview.mjs`, `git status --porcelain=v2 --branch`, `git worktree list --porcelain`, `git log --graph --decorate --oneline -n 40`; repository files; GitHub issue, PR, release and check-run metadata. Pagination is mandatory for “full” coverage; if it cannot be completed, label the sample. +- **Tier 1, targeted:** `git merge-base --is-ancestor`, `git rev-list --left-right --count BASE...HEAD`, `git diff BASE...HEAD --name-status`, per-PR files, tests, configuration routing, feature flags, GitHub reviews and CI jobs. Preserve the exact accepted source SHA. +- **Tier 2, explicit acceptance:** focused tests, source/PTY behavior repro, cross-shell tests, dispatch CI, security review, human physical Ghostty/Terminal.app QA. Execute only when the user and host resources permit. No automatic heavy full-suite runs. + +## Status/evidence table + +| Issue and acceptance item | Expected behavior | Branch/SHA and source location | Test and OS | Exact-head CI | Physical QA | Actual status | Missing evidence | +| --- | --- | --- | --- | --- | --- | --- | --- | +| #ID: one item | observable contract | SHA:path:line or not located | test / skipped / none | pass/fail/not run | passed/pending/N/A | isolated/integrated/etc | next gate | + +For every item distinguish **source code exists**, **user can reach it**, **correct operation tested**, **release integrated**, **physically accepted**, **publicly published**. Stronger sources supersede stale tracker language, but contradictions must still be recorded. + +## Branch/history hygiene + +1. Record default, dev, candidates, tags and local remote-tracking references with exact SHAs and timestamps. A source worktree may exist outside the checkout. Inspect `git worktree list --porcelain` before diagnosing stale branches. +2. Identify divergent branches by ahead/behind; check whether accepted commits are ancestors of the intended integration target. For squash/cherry-pick cases compare equivalent patches/final trees. Do not infer a feature is absent solely from non-ancestry. +3. Check dirty state, untracked files, missing upstream, shallow clones, wrong PR base, active worktree collisions, obsolete release candidate heads, PR required checks, skipped platforms and unresolved reviews. **No `git fetch --prune`, `git clean`, force-push, deletion or migration.** +4. Look for duplicate independent work (same functionality on different active branches) and explicitly nominate an integration owner and dependency order; never merge them autonomously. +5. Physical QA and manual release approvals must stay open even when automated CI is green. + +## Implemented vs marked complete + +- Audit high-impact *closed* issues and milestone checklists for “research completed” vs “product implemented”; independently inspect source and later PR evidence before proposing issue-state corrections. +- Audit high-impact *open* issues for stale “not yet implemented” text that newer commits supersede. +- For each behavior, trace CLI parser and help → route/controller → authority/state → optional service/protocol → persistence → UI/clipboard → tests and negative cases. A module tested in isolation does not establish production wiring. +- Verify default-off vs default-on, old settings migrations, deprecated aliases, NO_COLOR/Safe/narrow, raw output and secret handling, keyboard focus, interrupted readers and detachment where relevant. +- Preserve exact release status; a merged candidate isn't necessarily a tag/published release. Avoid hardcoded historical test counts and outdated issue language. + +## Community, dependencies and security + +- Confirm README, LICENSE, CONTRIBUTING, CODE_OF_CONDUCT, SUPPORT, SECURITY, issue/PR templates and ACCESSIBILITY.md on the **default branch**. A community profile usually won't detect a file present only on an unmerged branch. +- Confirm npm `package.json` and `package-lock.json` root declared ranges, and GitHub Dependency graph parsing from default branch. Check Advanced Security settings and initial processing wait; look under Insights → Dependency graph → Dependencies. The Network graph is branches/forks, not dependencies. Don't regenerate the lockfile just for an empty graph. +- Check Dependabot configuration and open security alerts when authorized; use OpenSSF Scorecard for security posture only. No scan can certify acceptance criteria or complete secret redaction. +- Accessibility: text independent of color, Safe/NO_COLOR, keyboard/focus/cancel behavior, reduced motion, small widths, alternate screen, physical host conflicts and limitations of terminal screen readers. Don't claim WCAG certification without a real assessment. +- Review unsafe ownership, uncontrolled PTY input, raw-copy contamination, archive restoration, plugin execution, stale approvals and redaction acceptance. Include only safe examples, never real credentials. + +## Report outline + +1. Scope: date/time, tool access, GitHub pagination coverage, local worktrees observed/not observed, SHA per ref. +2. Summary: P0 defects or red release gates; P1 missing integration/security; P2 docs/tracker drift; research/deferred distinct. +3. Feature matrix: untouched, partial, implemented isolated, candidate, automated verified, physical verified, released. +4. Issue/PR reconciliation with exact citations, conflicting claims and strongest evidence. +5. Graph, branch ancestry, worktree ownership, active PRs and CI; no cleanup. +6. Dependencies/community/accessibility/license, document accuracy and unsupported platform claims. +7. Minimum next actions and explicit “not tested / not observable”. + +## NMSh constraints + +Keep NMSh a frontend on a persistent real PTY shell with zsh/Bash/Fish; maintain raw copy, input ownership, stale-review refusal and release-branch isolation. Track Fish 3.7 typeahead, Ghostty/Terminal.app QA, and withheld v0.19/v0.20 releases as gates, not retroactively passed tasks. The separate smooth-scrolling research is intentionally deferred from production until performance evidence justifies it. diff --git a/.github/ACCESSIBILITY.md b/.github/ACCESSIBILITY.md new file mode 100644 index 00000000..9ae17274 --- /dev/null +++ b/.github/ACCESSIBILITY.md @@ -0,0 +1,33 @@ +# Accessibility in notMyShell + +notMyShell (NMSh) is a terminal frontend over a persistent real shell. We aim to make its essential workflows usable from the keyboard, in narrow terminal windows, without relying on color or animation alone. This is a statement of **intent, known limitations and contribution guidance**, not a certification of WCAG conformance or comprehensive screen-reader compatibility. + +## Accessibility principles + +- **Keyboard first.** Primary actions should be available without a mouse; focus, cancellation, prompts and explicit confirmations should be legible. Focusing a menu item never authorizes executing its command. +- **Communicate status with words.** Distinguish running, awaiting input, paused, succeeded, failed and interrupted states using text and clear glyphs as well as color, with factual exit status where appropriate. +- **Support different terminal capabilities.** Test narrow widths, text wrapping, Safe/ASCII glyph fallbacks, `NO_COLOR`, low-color terminals, and reduced visual effects. Keep relevant content separate from decorative chrome. +- **Respect motion preferences.** Motion and screen effects should be optional and not the only way to learn the state of a command. +- **Preserve terminal ownership.** Fullscreen or interactive tools, hidden input, shell readers and password prompts require correct input handoff; NMSh should not steal those keys. +- **Keep output faithful.** Copying or exporting command output should not add NMSh-specific visual escape sequences or omit meaningful raw text. + +## Testing and present limitations + +NMSh's behavior depends on the operating system, real shell, terminal host and any multiplexer. Ghostty and macOS Terminal.app are important targets; supported Linux configurations also need testing. Keyboard shortcuts can be intercepted by host software, and not every UI surface has completed physical verification. + +**We do not currently claim comprehensive screen-reader support or formal WCAG conformance.** Screen-reader behavior and terminal accessibility vary. Treat the project's issue tracker and physical release-QA checklists as the authoritative record of test coverage; automated tests and screenshots are not equivalent to physical keyboard or assistive-technology testing. + +## Areas to improve + +- Validate keyboard focus, escape/cancel paths, modal handoffs and alternative shortcuts across terminal hosts and multiplexers. +- Test screen readers and assistive technology in real supported configurations. +- Verify Safe/NO_COLOR, narrow rendering, reduced motion, contrast, glyph fallback, mouse alternatives and alternate-screen compatibility for new features. +- Report known gaps accurately rather than marking untested accessibility criteria passed. + +## Report an accessibility issue + +Please [open an accessibility issue](https://github.com/raiseCatError/notMyShell/issues/new/choose) with NMSh version/commit, OS, terminal emulator, shell, screen-reader or assistive technology if relevant, text/glyph/motion settings, steps to reproduce, expected behavior and actual barrier. Report whether an equivalent keyboard path exists. + +**Do not publish secrets, private terminal history, credentials or sensitive output.** Report vulnerabilities or unexpected secret disclosure confidentially using [SECURITY.md](../SECURITY.md). General help is covered by [SUPPORT.md](../SUPPORT.md), and code contributions by [CONTRIBUTING.md](../CONTRIBUTING.md). + +Accessibility reports and tests are welcome. Please preserve NMSh's existing PTY, exact-output, safe keyboard/input ownership and optional visual-presentation guarantees. Revisit this statement whenever major supported-host or input-interface behavior changes. diff --git a/docs/development/freview.md b/docs/development/freview.md new file mode 100644 index 00000000..acc8149d --- /dev/null +++ b/docs/development/freview.md @@ -0,0 +1,31 @@ +# freview — full repository review and hygiene audit + +The cross-agent skill is [`.agents/skills/freview/SKILL.md`](../../.agents/skills/freview/SKILL.md), with its [evidence matrix](../../.agents/skills/freview/references/review-matrix.md). Ask any compatible coding agent: **“Use freview to audit the repository, all issues and PRs, branch ancestry, unverified implementation and release gates.”** This is an **on-demand, read-only audit**, not automatic maintenance or a nightly CI workflow. + +## Local inspection + +Run on a checkout: + +```sh +node scripts/dev/freview.mjs --base origin/dev +node scripts/dev/freview.mjs --base origin/dev --json +``` + +Use `--base dev` if that is the only verified local ref. The script never fetches, installs packages or changes files. It reports exact HEAD and base SHA, divergence, local branches/upstreams, active worktrees, dirty state, whitespace checks, root npm manifest/lock alignment and community-file presence. It cannot inspect private worktrees remotely, verify user-visible behavior, or confirm GitHub CI; the **skill** handles remote reconciliation with linked evidence. + +## Why a feature can be “done” and still be pending + +Completion is a chain: **specified → coded → reachable → integrated → exact-head automated verified → physically accepted → released**. Record the exact point reached. A closed research issue does not promise runtime support; unchecked acceptance boxes are review leads, not conclusive proof of absent code. A successful local test does not make a failing GitHub check green. A candidate release is not a published GitHub Release. The report must expose scope and limitations. + +## Dependency graph and network graph + +GitHub's **Network** view is for Git history/fork topology. The **Dependency graph** under Insights → Dependency graph describes external package dependencies; it is not a graph of TypeScript imports or which app features are connected. NMSh's default branch already provides `package.json` and `package-lock.json`. After enabling the feature, allow for the initial processing period. If still blank, check access/security settings, default branch and parsing status before changing manifests. See [GitHub dependency graph documentation](https://docs.github.com/en/code-security/how-tos/secure-your-supply-chain/secure-your-dependencies/enable-dependency-graph). + +## Complementary tools and inspiration + +- [OpenSSF Scorecard](https://github.com/ossf/scorecard) checks repository security posture, not issue implementation completeness. +- [Repolinter](https://github.com/todogroup/repolinter) codifies policy for repository files and community conventions; not branch-to-acceptance reconciliation. +- [Claude-compatible codebase-review](https://github.com/amragl/codebase-review) surveys code quality and project hygiene; compare its design, but don't run external scripts automatically. +- [GitHub Community Standards](https://docs.github.com/en/communities/setting-up-your-project-for-healthy-contributions/about-community-profiles-for-public-repositories) recognizes public-project contribution documentation. + +The skill has **no external npm/runtime dependencies** beyond the Node and Git already used by NMSh. It will not edit, delete, prune or merge anything as a side effect. diff --git a/scripts/dev/freview.mjs b/scripts/dev/freview.mjs new file mode 100644 index 00000000..d70a1877 --- /dev/null +++ b/scripts/dev/freview.mjs @@ -0,0 +1,138 @@ +#!/usr/bin/env node +// freview: read-only local facts. Never fetch, mutate refs, run builds or delete files. +import {execFileSync} from 'node:child_process'; +import {existsSync, readFileSync} from 'node:fs'; +import {join} from 'node:path'; + +const args = process.argv.slice(2); +let base = null; +let json = false; +for (let i = 0; i < args.length; i++) { + if (args[i] === '--json') json = true; + else if (args[i] === '--base' && args[i + 1]) base = args[++i]; + else if (args[i].startsWith('--base=')) base = args[i].slice(7); + else if (args[i] === '--help') { + console.log('Usage: node scripts/dev/freview.mjs [--base REF] [--json]'); + process.exit(0); + } else { + console.error('Unknown/incomplete argument: ' + args[i]); + process.exit(2); + } +} +const gitOptions = {cwd: process.cwd(), encoding: 'utf8', maxBuffer: 8 * 1024 * 1024, timeout: 12000, stdio: ['ignore', 'pipe', 'pipe']}; +function git(input, optional = false) { + try { return execFileSync('git', input, gitOptions).trimEnd(); } + catch (error) { + if (optional) return null; + throw Error('git ' + input[0] + ': ' + (error.stderr?.toString().trim() || error.message)); + } +} +function sha(ref) { return git(['rev-parse', '--verify', '--quiet', ref + '^{commit}'], true); } +function divergence(a, b) { + const line = git(['rev-list', '--left-right', '--count', a + '...' + b], true); + if (!line) return null; + const [behind, ahead] = line.trim().split(/\s+/).map(Number); + return {ahead, behind}; +} +function isAncestor(a, b) { + try { + execFileSync('git', ['merge-base', '--is-ancestor', a, b], {...gitOptions, stdio: 'ignore'}); + return true; + } catch (e) { return e.status === 1 ? false : null; } +} +function statusCount(str) { + const parts = str.split('\0'); + let tracked = 0, untracked = 0; + for (let i = 0; i < parts.length; i++) { + const p = parts[i]; + if (!p) continue; + if (p.startsWith('??')) untracked++; + else { + tracked++; + if (/^[ RC][ RC] /.test(p)) i++; // rename has an extra old-path record in -z + } + } + return {tracked, untracked, clean: tracked + untracked === 0}; +} +function worktreeList(str) { + return str.split(/\n\n+/).filter(Boolean).map(block => { + const lines = block.split('\n'); + const val = key => lines.find(l => l.startsWith(key + ' '))?.slice(key.length + 1) || null; + return {path: val('worktree'), sha: val('HEAD'), branch: val('branch')?.replace(/^refs\/heads\//, '') || null, detached: lines.includes('detached'), locked: lines.some(l => l.startsWith('locked'))}; + }); +} +function getCommunity(root) { + const found = (...paths) => paths.some(p => existsSync(join(root, p))); + return { + readme: found('README.md'), license: found('LICENSE', 'LICENSE.md', 'LICENSE.txt'), + contributing: found('CONTRIBUTING.md', '.github/CONTRIBUTING.md'), + codeOfConduct: found('CODE_OF_CONDUCT.md', '.github/CODE_OF_CONDUCT.md'), + security: found('SECURITY.md', '.github/SECURITY.md'), + support: found('SUPPORT.md', '.github/SUPPORT.md'), + accessibility: found('.github/ACCESSIBILITY.md', 'ACCESSIBILITY.md', 'docs/ACCESSIBILITY.md'), + issueTemplates: found('.github/ISSUE_TEMPLATE'), + prTemplate: found('.github/pull_request_template.md', 'PULL_REQUEST_TEMPLATE.md'), + }; +} +function getNpm(root) { + const manifest = join(root, 'package.json'), lock = join(root, 'package-lock.json'); + if (!existsSync(manifest)) return {present: false, note: 'No root npm manifest'}; + if (!existsSync(lock)) return {present: true, lockPresent: false}; + try { + const pkg = JSON.parse(readFileSync(manifest, 'utf8')); + const locked = JSON.parse(readFileSync(lock, 'utf8')); + const lockRoot = locked.packages?.['']; + if (!lockRoot) return {present: true, lockPresent: true, lockfileVersion: locked.lockfileVersion, note: 'Cannot compare missing packages[\"\"]'}; + const drift = []; + for (const part of ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies']) { + const want = pkg[part] || {}, have = lockRoot[part] || {}; + for (const [name, range] of Object.entries(want)) if (have[name] !== range) drift.push({part, name, problem: 'missing/mismatched'}); + for (const name of Object.keys(have)) if (!(name in want)) drift.push({part, name, problem: 'extra root lock entry'}); + } + return {present: true, lockPresent: true, lockfileVersion: locked.lockfileVersion, + direct: Object.keys(pkg.dependencies || {}).length, dev: Object.keys(pkg.devDependencies || {}).length, + rootAligned: drift.length === 0, drift: drift.slice(0, 50), truncated: drift.length > 50}; + } catch (error) { + return {present: true, lockPresent: true, error: 'Unparseable package metadata: ' + error.message}; + } +} +try { + const root = git(['rev-parse', '--show-toplevel']); + const head = git(['rev-parse', 'HEAD']); + const branch = git(['symbolic-ref', '--quiet', '--short', 'HEAD'], true) || '(detached)'; + const upstream = git(['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{upstream}'], true); + const shallow = git(['rev-parse', '--is-shallow-repository']) === 'true'; + base ||= ['origin/dev', 'dev', 'origin/master', 'master'].find(x => sha(x)) || null; + const baseSha = base && sha(base); + const worktrees = worktreeList(git(['worktree', 'list', '--porcelain'])); + const refs = git(['for-each-ref', '--format=%(refname:short)%00%(objectname)%00%(committerdate:iso-strict)%00%(upstream:short)%00%(upstream:track)', 'refs/heads']); + const branches = refs.split('\n').filter(Boolean).map(line => { + const [name, id, lastCommit, tracking, trackStatus] = line.split('\0'); + return {name, sha: id, lastCommit, upstream: tracking || null, upstreamGone: Boolean(trackStatus?.includes('[gone]')), + worktrees: worktrees.filter(w => w.branch === name).map(w => w.path), + divergence: baseSha ? divergence(baseSha, id) : null, + ancestorOfBase: baseSha ? isAncestor(id, baseSha) : null}; + }); + const report = {kind: 'freview-readonly-local', collectedAt: new Date().toISOString(), root, head, branch, upstream, + shallow, base: baseSha ? {ref: base, sha: baseSha, divergence: divergence(baseSha, head)} : null, + status: statusCount(git(['status', '--porcelain=v1', '-z', '--untracked-files=normal'])), + whitespace: {unstagedPass: git(['diff', '--check'], true) !== null, stagedPass: git(['diff', '--cached', '--check'], true) !== null}, + worktrees, branches, community: getCommunity(root), npm: getNpm(root), + notVerified: ['GitHub issue/PR/CI/release status and remote freshness', 'Physical QA, security and user-observable implementation', 'Uncommitted files in other inaccessible worktrees', 'Squash/cherry-pick equivalence; ancestry alone is not enough']}; + if (json) console.log(JSON.stringify(report, null, 2)); + else { + console.log('# freview local audit — ' + report.collectedAt); + console.log('HEAD: ' + branch + ' @ ' + head + '; upstream: ' + (upstream || 'none') + '; shallow: ' + shallow); + console.log('Base: ' + (baseSha ? base + ' @ ' + baseSha + ' ' + JSON.stringify(report.base.divergence) : 'not available')); + console.log('Local state: ' + JSON.stringify(report.status) + '; whitespace: ' + JSON.stringify(report.whitespace)); + console.log('Branches: ' + branches.length + '; worktrees: ' + worktrees.length); + for (const b of branches.filter(b => b.upstreamGone || (b.ancestorOfBase && b.name !== base))) + console.log('Review (never automatically delete) ' + b.name + ': upstream gone=' + b.upstreamGone + ', ancestor=' + b.ancestorOfBase + ', active worktrees=' + b.worktrees.length); + console.log('Community: ' + JSON.stringify(report.community)); + console.log('npm: ' + JSON.stringify(report.npm)); + console.log('LIMIT: Remote issues/PRs/CI/releases and physical QA require separate evidence.'); + } +} catch (e) { + console.error('freview could not complete snapshot: ' + e.message); + process.exitCode = 1; +}