Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,11 @@ jobs:
- name: Install Cosign
uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1

- name: Set up Node
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: 24

- name: Verify published GitHub release artifacts
env:
GH_TOKEN: ${{ github.token }}
Expand Down
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ CARGO_AUDIT_VERSION ?= 0.22.1
CARGO_DENY_VERSION ?= 0.19.4
CARGO_LLVM_COV_VERSION ?= 0.8.5

.PHONY: doctor install-dev-tools test test-fast test-security test-ux test-release test-local test-prepush test-release-install fmt clippy cargo-test cargo-nextest cargo-doc cargo-audit cargo-deny cargo-llvm-cov coverage-check fuzz-check test-junit trunk-check trunk-fix trunk-flaky-validate ci-scripts-test gitleaks raycast-install raycast-test raycast-test-junit raycast-audit raycast-lint raycast-build raycast-store-check raycast-verify npm-package-install npm-package-test npm-package-audit npm-package-pack npm-package-verify docs-reference docs-reference-check docs-qa site-install site-test site-audit site-build site-verify demo-assets tui-media release-snapshot verify build install-local clean-reports
.PHONY: doctor install-dev-tools test test-fast test-security test-ux test-release test-local test-prepush test-release-install fmt clippy cargo-test cargo-nextest cargo-doc cargo-audit cargo-deny cargo-llvm-cov coverage-check fuzz-check test-junit trunk-check trunk-fix trunk-flaky-validate ci-scripts-test gitleaks raycast-install raycast-test raycast-test-junit raycast-audit raycast-lint raycast-build raycast-store-check raycast-verify npm-package-install npm-package-test npm-package-audit npm-package-pack npm-package-verify docs-reference docs-reference-check docs-qa site-install site-test site-audit site-build site-verify demo-assets tui-media homebrew-formula release-snapshot verify build install-local clean-reports

doctor:
bash scripts/dev-doctor.sh
Expand Down Expand Up @@ -159,6 +159,10 @@ demo-assets:
tui-media:
node scripts/generate-tui-media.mjs

homebrew-formula:
@if [ -z "$${VERSION:-}" ]; then echo "VERSION is required, for example: make homebrew-formula VERSION=0.1.6" >&2; exit 2; fi
node scripts/generate-homebrew-formula.mjs --version "$${VERSION}" --checksums dist/checksums.txt

release-snapshot: build
bash scripts/release-snapshot-rust.sh

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ Nightward answers the practical questions first:
- Redacted plan-only remediation exports for parseable MCP config findings.
- Read-only snapshot plans plus confirmed portable config snapshot creation.
- Reusable GitHub Action for scan, policy, and SARIF modes.
- Raycast extension for Dashboard, Findings, Analysis, Provider Doctor, Nightward Actions, Explain Finding/Signal, Fix Plan/Analysis export, and report-folder access.
- Raycast extension for Dashboard, Findings, Analysis, Compare Reports, Provider Doctor, Nightward Actions, Explain Finding/Signal, Fix Plan/Analysis export, and report-folder access.
- Stdio MCP server for AI clients that need local scan, analysis, finding, rule, provider, policy, report, prompt, fix-plan, and bounded action context.
- User-level nightly scan scheduling for macOS launchd and Linux systemd user timers.
- No telemetry, no cloud dashboard, and no default network calls from Nightward runtime.
Expand Down
20 changes: 12 additions & 8 deletions docs/distribution.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,19 @@ Nightward distribution should optimize for trust first, then convenience.
3. Source builds with `make install-local`. Development-only.
4. Trunk plugin import from a release tag. Shipped.
5. GitHub Action release tags. Shipped.
6. Homebrew tap.
7. Nix flake/package.
8. Scoop and WinGet.
9. mise and aqua.
6. Homebrew formula generation from signed release checksums. Scaffolded.
7. Homebrew tap publication.
8. Nix flake/package.
9. Scoop and WinGet.
10. mise and aqua.

Docker is deferred until Nightward has a useful local report browser. A container is not a good default for safely scanning a user's HOME directory.

## Homebrew Tap Plan
## Homebrew Tap Support

Homebrew is straightforward once the release archive layout stays stable. Add a dedicated tap repository, then generate a formula from the signed GitHub Release archive and checksum data. The formula should install both `nightward` and `nw`, include a lightweight `nightward --version` test, and avoid becoming a separate packaging implementation.
`scripts/generate-homebrew-formula.mjs` generates a tap-ready formula from the signed GitHub Release checksum file. The formula points at the existing `nightward_<version>_<os>_<arch>.tar.gz` archives, installs both `nightward` and `nw`, and tests both command names with `--version`.

The release verifier runs the generator after Cosign verifies `checksums.txt.sigstore.json` and `sha256sum` validates the downloaded archive. That keeps Homebrew support tied to the release artifact shape instead of creating a second packaging implementation. A dedicated tap repository is still a separate publication step.

## NPM Posture

Expand All @@ -38,5 +41,6 @@ The npm package is `@jsonbored/nightward`. It is published through npm trusted p
4. Run local verification.
5. Create a signed SemVer tag.
6. Verify GitHub release artifacts.
7. Verify npm metadata and launcher behavior with `scripts/verify-npm-release.sh`.
8. Update OpenSSF badge evidence.
7. Generate/check the Homebrew formula from signed release checksums.
8. Verify npm metadata and launcher behavior with `scripts/verify-npm-release.sh`.
9. Update OpenSSF badge evidence.
4 changes: 2 additions & 2 deletions docs/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ The package should not use a long-lived npm token. It should publish through Git

## Deferred Channels

These are useful, but should wait until signed GitHub Release artifacts prove stable across patch releases:
These are useful, but still need a publication path:

- Homebrew tap
- Homebrew tap repository. Formula generation is scaffolded in `scripts/generate-homebrew-formula.mjs` and verified from signed release checksums.
- Nix package
- mise/aqua registry entries
- Docker image for report browsing
4 changes: 4 additions & 0 deletions docs/raycast-extension.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ integrations/raycast
- `Nightward Status`: compact menu-bar finding count, plus full critical/high/total counts, analysis signals, provider warnings, scheduled-report state, and latest-report access in the dropdown.
- `Nightward Findings`: searchable findings with a severity filter, detail pane, scoped fix-plan exports, reviewed-policy-ignore snippets, redacted evidence copy, and open-doc actions.
- `Nightward Analysis`: built-in offline signals plus explicitly selected providers.
- `Compare Nightward Reports`: read-only diff of the latest two saved reports from `nw report history`.
- `Nightward Provider Doctor`: optional provider availability, privacy posture, install guidance for missing tools, and Raycast Analysis enable/disable controls.
- `Nightward Actions`: preview and apply confirmed provider, policy, schedule, backup, cleanup, and setup actions.
- `Explain Nightward Finding`: detail view for a known finding ID.
Expand Down Expand Up @@ -42,6 +43,8 @@ The extension uses `execFile`, not a shell, for local Nightward commands. It cal
- `fix export --rule <rule> --format markdown`
- `analyze [--with providers] [--online] --json`
- `analyze finding <id> --json`
- `report history --json`
- `report diff --from <base> --to <head>`
- `providers doctor [--with providers] [--online] --json`

Write-capable calls are limited to `actions apply <id> --confirm` through the shared action registry. Provider Doctor previews `provider.install.<name>` and applies that registry action after explicit confirmation; it no longer runs package-manager commands through a shell. No Raycast command runs restore, Git, or hidden shell mutation.
Expand Down Expand Up @@ -73,6 +76,7 @@ Manual UI validation must use a fixture `Home Override`, not a real local home,
- Menu-bar status shows finding, analysis, provider-warning, and schedule counters; its actions open existing read-only commands, open the latest report when present, and copy a redacted summary.
- Findings search/filter/detail panes render redacted evidence, docs actions, scoped fix-plan exports, and reviewed-policy-ignore snippets.
- Analysis renders built-in signals, selected provider output, provider warnings, and blocked-online-provider state.
- Compare Reports renders the latest two fixture reports, handles missing-history errors, and only offers copy/open/refresh actions.
- Provider Doctor shows provider status, install guidance, action-registry provider CLI installation, and enable/disable controls for Raycast Analysis without running online-capable providers unless explicit opt-in is enabled.
- Nightward Actions lists action IDs, risk, writes, commands, blocked reasons, and applies only after confirmation.
- Export commands copy redacted Markdown and do not mutate local config.
Expand Down
2 changes: 2 additions & 0 deletions docs/release.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ The release workflow verifies the published GitHub archive before npm publish:
bash scripts/verify-release-archive.sh vX.Y.Z
```

That verifier also generates a Homebrew formula from the signed `checksums.txt` file and checks that the formula installs/tests both `nightward` and `nw`.

The npm job then installs the packed npm tarball and runs both command names before publishing.

After npm publish, verify package metadata and launcher install behavior:
Expand Down
4 changes: 3 additions & 1 deletion docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ make verify
- Scheduler tests verify generated launchd, systemd user timer, and cron text without installing schedules.
- TUI tests cover fixed terminal rendering behavior, redaction boundaries, and embedded OpenTUI layout helpers.
- Scheduler tests cover report history ordering, finding counts, non-report filtering, and symlink skipping without installing timers.
- Raycast extension tests cover pure redaction/formatting helpers, safe command execution wrappers, and Provider Doctor install routing through the shared action registry instead of direct shell execution.
- Raycast extension tests cover pure redaction/formatting helpers, safe command execution wrappers, report-history compare loading/error handling, and Provider Doctor install routing through the shared action registry instead of direct shell execution.
- `cargo fmt`, `cargo clippy -D warnings`, `cargo test`, optional `cargo audit`/`cargo deny`, Gitleaks, and coverage checks are part of the local verification bar.
- `make coverage-check` enforces the practical coverage target when `cargo-llvm-cov` is available, and always runs the Rust workspace tests.
- `make ci-scripts-test` verifies repository-maintained CI helper scripts such as DCO checking, action path validation, and release-script input validation.
Expand All @@ -119,6 +119,8 @@ CI validates that the JUnit report is parseable for every pull request. Trunk up

`make release-snapshot` builds the Rust release binaries, creates a local archive, writes `checksums.txt`, and emits a lightweight SBOM placeholder for archive-shape validation. Real release signing remains restricted to the tag-driven release workflow.

`scripts/verify-release-archive.sh` generates the Homebrew formula only after signed checksum verification has passed. `make ci-scripts-test` keeps that helper wired to the current archive/checksum shape.

## Raycast Extension

The extension has its own npm package under `integrations/raycast`.
Expand Down
1 change: 1 addition & 0 deletions integrations/raycast/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Unreleased

- Add a top-level Compare Nightward Reports command for read-only latest-report diffs.
- Use a compact menu-bar finding count and move severity/provider detail into the dropdown.
- Add scoped finding and rule fix-plan copy actions plus reviewed-policy-ignore snippets.
- Redact additional provider token-shaped values in Raycast-rendered text.
Expand Down
1 change: 1 addition & 0 deletions integrations/raycast/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ This extension is read-only until a user invokes the shared Nightward action reg
- `Nightward Status`: compact menu-bar finding count, plus detailed findings, analysis signals, provider warnings, and scheduled-report state in the dropdown.
- `Nightward Findings`: searchable findings with severity filters, detail panes, scoped fix-plan exports, reviewed-policy-ignore snippets, and redacted evidence copy.
- `Nightward Analysis`: built-in offline analysis plus any providers explicitly selected in Provider Doctor.
- `Compare Nightward Reports`: top-level read-only comparison of the latest two saved Nightward reports.
- `Nightward Provider Doctor`: provider availability, privacy posture, action-registry install guidance for missing tools, and Raycast Analysis enable/disable controls.
- `Nightward Actions`: preview and apply confirmation-gated provider, policy, schedule, backup, cleanup, and setup actions from the shared Nightward action registry.
- `Explain Nightward Finding`: detail view for a specific finding ID.
Expand Down
7 changes: 7 additions & 0 deletions integrations/raycast/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@
"mode": "view",
"keywords": ["analysis", "signals", "security", "privacy", "trust"]
},
{
"name": "compare-reports",
"title": "Compare Nightward Reports",
"description": "Compare the latest two saved Nightward reports without mutating local config.",
"mode": "view",
"keywords": ["reports", "compare", "history", "diff", "security"]
},
{
"name": "provider-doctor",
"title": "Nightward Provider Doctor",
Expand Down
4 changes: 4 additions & 0 deletions integrations/raycast/raycast-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ declare namespace Preferences {
export type Findings = ExtensionPreferences & {}
/** Preferences accessible in the `analysis` command */
export type Analysis = ExtensionPreferences & {}
/** Preferences accessible in the `compare-reports` command */
export type CompareReports = ExtensionPreferences & {}
/** Preferences accessible in the `provider-doctor` command */
export type ProviderDoctor = ExtensionPreferences & {}
/** Preferences accessible in the `actions` command */
Expand All @@ -53,6 +55,8 @@ declare namespace Arguments {
export type Findings = {}
/** Arguments passed to the `analysis` command */
export type Analysis = {}
/** Arguments passed to the `compare-reports` command */
export type CompareReports = {}
/** Arguments passed to the `provider-doctor` command */
export type ProviderDoctor = {}
/** Arguments passed to the `actions` command */
Expand Down
63 changes: 63 additions & 0 deletions integrations/raycast/src/compare-reports.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {
Action,
ActionPanel,
Detail,
Icon,
getPreferenceValues,
} from "@raycast/api";
import { usePromise } from "@raycast/utils";
import {
latestReportPair,
normalizePreferences,
reportHistory,
reportsDir,
reportsDirExists,
} from "./nightward";
import { ReportCompareDetail } from "./report-compare";

export default function Command() {
const runtime = normalizePreferences(getPreferenceValues());
const { data, error, isLoading, revalidate } = usePromise(async () => {
const history = await reportHistory(runtime);
return latestReportPair(history);
});

if (error) {
const reportDir = reportsDir(runtime.homeOverride);
const canOpenReports = reportsDirExists(runtime.homeOverride);
return (
<Detail
markdown={`# Compare Nightward Reports\n\n${error.message}`}
actions={
<ActionPanel>
<Action
title="Refresh"
icon={Icon.ArrowClockwise}
onAction={revalidate}
/>
{canOpenReports ? (
<Action.ShowInFinder
title="Open Report Folder"
path={reportDir}
/>
) : null}
<Action.CopyToClipboard
title="Copy Reports Path"
content={reportDir}
/>
</ActionPanel>
}
/>
);
}

if (!data) {
return (
<Detail isLoading={isLoading} markdown="# Compare Nightward Reports" />
);
}

return (
<ReportCompareDetail runtime={runtime} base={data.base} head={data.head} />
);
}
96 changes: 1 addition & 95 deletions integrations/raycast/src/dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ import {
findingTitle,
fixPlanTotal,
maxSeverity,
reportDiffMarkdown,
reportDiffSubtitle,
severityColor,
sortedFindings,
} from "./format";
Expand All @@ -26,16 +24,14 @@ import {
doctor,
fixPlan,
normalizePreferences,
reportDiff,
reportsDir,
scan,
type RuntimeOptions,
} from "./nightward";
import { ReportCompareDetail } from "./report-compare";
import type {
AnalysisReport,
DoctorReport,
FixPlan,
ReportRecord,
ScanReport,
} from "./types";

Expand Down Expand Up @@ -459,96 +455,6 @@ function ScheduleDetail({ doctor }: { doctor: DoctorReport }) {
);
}

function ReportCompareDetail({
runtime,
base,
head,
}: {
runtime: RuntimeOptions;
base: ReportRecord;
head: ReportRecord;
}) {
const { data, error, isLoading, revalidate } = usePromise(() =>
reportDiff(runtime, base.path, head.path),
);
if (error) {
return (
<Detail
markdown={`# Report Compare\n\n${error.message}`}
actions={
<ActionPanel>
<Action
title="Refresh"
icon={Icon.ArrowClockwise}
onAction={revalidate}
/>
<Action.ShowInFinder title="Show Latest Report" path={head.path} />
<Action.ShowInFinder
title="Show Previous Report"
path={base.path}
/>
</ActionPanel>
}
/>
);
}
if (!data) {
return <Detail isLoading={isLoading} markdown="# Report Compare" />;
}
const markdown = reportDiffMarkdown(data);
return (
<Detail
isLoading={isLoading}
markdown={markdown}
metadata={
<Detail.Metadata>
<Detail.Metadata.TagList title="Change">
<Detail.Metadata.TagList.Item
text={reportDiffSubtitle(data)}
color={severityColor(data.summary.max_added_severity)}
/>
</Detail.Metadata.TagList>
<Detail.Metadata.Separator />
<Detail.Metadata.Label
title="Added"
text={String(data.summary.added)}
/>
<Detail.Metadata.Label
title="Removed"
text={String(data.summary.removed)}
/>
<Detail.Metadata.Label
title="Changed"
text={String(data.summary.changed)}
/>
<Detail.Metadata.Label
title="Max Added"
text={data.summary.max_added_severity}
/>
<Detail.Metadata.Separator />
<Detail.Metadata.Label title="Base" text={basename(base.path)} />
<Detail.Metadata.Label title="Head" text={basename(head.path)} />
</Detail.Metadata>
}
actions={
<ActionPanel>
<Action.CopyToClipboard
title="Copy Compare Markdown"
content={markdown}
/>
<Action
title="Refresh"
icon={Icon.ArrowClockwise}
onAction={revalidate}
/>
<Action.ShowInFinder title="Show Latest Report" path={head.path} />
<Action.ShowInFinder title="Show Previous Report" path={base.path} />
</ActionPanel>
}
/>
);
}

function FixPlanDetail({ plan }: { plan: FixPlan }) {
const lines = [
"# Fix Plan",
Expand Down
Loading
Loading