diff --git a/.agents/test-matrix.yml b/.agents/test-matrix.yml index 7197d880..cfd17b00 100644 --- a/.agents/test-matrix.yml +++ b/.agents/test-matrix.yml @@ -10,7 +10,7 @@ rules: - "Tests/*.swift" - "Tests/FastTests.manifest" checks: - - "bash build.sh" + - "bash build.sh --no-open" - "bash run-tests.sh" - paths: @@ -18,7 +18,7 @@ rules: - "Sources/TranscriptedCore/**" - "Tests/Integration/**" checks: - - "bash build.sh" + - "bash build.sh --no-open" - "bash run-tests.sh" - "bash run-integration-smoke.sh" @@ -29,6 +29,52 @@ rules: checks: - "bash run-e2e-smoke.sh" + - paths: + - "build-deps.sh" + - "scripts/entrypoints/build-deps.sh" + checks: + - "scripts/dev/agent-preflight.sh" + - "bash -n build-deps.sh" + - "bash -n scripts/entrypoints/build-deps.sh" + - "bash build-deps.sh --force" + + - paths: + - "build.sh" + - "scripts/entrypoints/build.sh" + checks: + - "scripts/dev/agent-preflight.sh" + - "bash -n build.sh" + - "bash -n scripts/entrypoints/build.sh" + - "bash build.sh --no-open" + + - paths: + - "run-tests.sh" + - "scripts/entrypoints/run-tests.sh" + - "Tests/FastTests.manifest" + checks: + - "scripts/dev/agent-preflight.sh" + - "bash -n run-tests.sh" + - "bash -n scripts/entrypoints/run-tests.sh" + - "bash run-tests.sh" + + - paths: + - "run-integration-smoke.sh" + - "scripts/entrypoints/run-integration-smoke.sh" + - "Tests/Integration/**" + checks: + - "scripts/dev/agent-preflight.sh" + - "bash -n run-integration-smoke.sh" + - "bash -n scripts/entrypoints/run-integration-smoke.sh" + - "bash run-integration-smoke.sh" + + - paths: + - "run-daily-audio-reliability.sh" + - "scripts/ops/daily-audio-reliability-check.sh" + checks: + - "scripts/dev/agent-preflight.sh" + - "bash -n run-daily-audio-reliability.sh" + - "bash -n scripts/ops/daily-audio-reliability-check.sh" + - paths: - "scripts/ops/transcripted-qa-bench.sh" - "scripts/ops/validate-meeting-corpus.py" @@ -39,6 +85,18 @@ rules: - "python3 -m py_compile scripts/ops/validate-meeting-corpus.py" - "python3 -m py_compile scripts/ops/compare-meeting-corpus.py" + - paths: + - "scripts/ops/agent-todo-runner.rb" + - "scripts/ops/agent-todo-launchagent.sh" + - "scripts/ops/qa-gate-check.sh" + - "scripts/ops/qa-gate-closeout.sh" + checks: + - "scripts/dev/agent-preflight.sh" + - "ruby -c scripts/ops/agent-todo-runner.rb" + - "bash -n scripts/ops/agent-todo-launchagent.sh" + - "bash -n scripts/ops/qa-gate-check.sh" + - "bash -n scripts/ops/qa-gate-closeout.sh" + - paths: - "Tests/TranscriptedCoreTests/LiveCaptureSmokeTests.swift" - "run-live-capture-smoke.sh" @@ -51,7 +109,7 @@ rules: - "Sources/TranscriptedCore/**" - "Tests/TranscriptedCoreTests/**" checks: - - "bash build.sh" + - "bash build.sh --no-open" - "bash run-tests.sh" - "bash run-integration-smoke.sh" - "swift test" @@ -62,7 +120,7 @@ rules: - "docs/sparkle-updates.md" - "docs/appcast.xml" checks: - - "bash build.sh" + - "bash build.sh --no-open" - "bash run-tests.sh" - paths: @@ -74,7 +132,7 @@ rules: - "Casks/**" - "docs/appcast.xml" checks: - - "bash build.sh" + - "bash build.sh --no-open" - "bash run-tests.sh" - "SKIP_NOTARIZATION=1 bash build-beta.sh " @@ -99,8 +157,11 @@ rules: - "AGENTS.md" - "CLAUDE.md" - "CONTRIBUTING.md" + - "WORKFLOW.md" - "docs/**" - ".agents/**" + - ".github/**" + - "scripts/dev/agent-preflight.sh" checks: - "scripts/dev/agent-preflight.sh" diff --git a/.github/ISSUE_TEMPLATE/agent_task.md b/.github/ISSUE_TEMPLATE/agent_task.md index de02b2ad..c08a65aa 100644 --- a/.github/ISSUE_TEMPLATE/agent_task.md +++ b/.github/ISSUE_TEMPLATE/agent_task.md @@ -19,3 +19,8 @@ assignees: "" ## Verification - [ ] + +## Queue note + +This template does not start the local runner by itself. After checking the +issue is ready for Codex, add the `agent todo` label. diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index b00d3d79..227efc85 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -36,6 +36,8 @@ What actually happened. **Diagnostics** If you have logs, screenshots, or exported diagnostics, attach them here. +Please redact transcripts, audio, meeting titles, speaker names, emails, tokens, +absolute paths, and private customer or meeting data before uploading. If this report is about the old standalone Transcripted app, say so explicitly and include the legacy ref you were using (`legacy/transcripted-standalone` or diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 5f754643..2fb6dec2 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -30,6 +30,8 @@ Any other approaches you've thought about. **Anything else?** Examples, screenshots, links, or things we should avoid. +Please do not include private transcripts, audio, meeting titles, speaker names, +emails, tokens, absolute paths, or customer data. If this request is specifically about the old standalone Transcripted app, say that explicitly so it can be triaged against the legacy branch/tag. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index af4f3d42..e74b2460 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -13,10 +13,12 @@ ## How I checked it -- [ ] `bash build.sh` +- [ ] `scripts/dev/agent-preflight.sh` +- [ ] `bash build.sh --no-open` - [ ] `bash run-tests.sh` -- [ ] Performance budget passed (`bash build.sh` runs the bundle gate; run `scripts/ops/performance-budget.rb --events "$HOME/Library/Application Support/Transcripted/logs/events.jsonl"` for runtime-sensitive changes) +- [ ] Performance budget passed (`bash build.sh --no-open` runs the bundle gate; run `scripts/ops/performance-budget.rb --events "$HOME/Library/Application Support/Transcripted/logs/events.jsonl"` for runtime-sensitive changes) - [ ] `bash run-integration-smoke.sh` if I touched `Sources/Meeting/` or `Sources/TranscriptedCore/` +- [ ] `swift test` if I touched `Package.swift`, `Sources/TranscriptedCore/`, or the public core seam - [ ] Manual check: ## Risk Review @@ -25,6 +27,9 @@ - [ ] Storage path or migration impact reviewed - [ ] Public-facing copy stays concrete and matches current product scope - [ ] Release/update impact reviewed (`CFBundleShortVersionString`, `docs/appcast.xml`, or user-facing caveats if applicable) +- [ ] Agent PRs link the issue/workpad and stay draft until human review +- [ ] UI changes include sanitized `.agent-review/visuals/` evidence +- [ ] No private transcripts, audio, tokens, personal paths, or customer data are included ## Notes diff --git a/.github/workflows/qa-gate-auto-close-bet88.yml b/.github/workflows/qa-gate-auto-close-bet88.yml index fc7ad59e..e2097241 100644 --- a/.github/workflows/qa-gate-auto-close-bet88.yml +++ b/.github/workflows/qa-gate-auto-close-bet88.yml @@ -13,7 +13,8 @@ jobs: ${{ github.event.issue.number == 428 && github.actor != 'github-actions[bot]' && - github.event.comment.user.login == 'r3dbars' + github.event.comment.user.login == 'r3dbars' && + contains(github.event.issue.labels.*.name, 'qa-gate-auto-close') }} runs-on: ubuntu-latest timeout-minutes: 5 @@ -52,7 +53,6 @@ jobs: with: script: | const issue_number = context.payload.issue.number; - const child_issue_number = 456; const owner = context.repo.owner; const repo = context.repo.repo; await github.rest.issues.update({ @@ -68,19 +68,6 @@ jobs: issue_number, body: "Auto-close: detected top-level PASS comment for BET-88 QA gate." }); - await github.rest.issues.update({ - owner, - repo, - issue_number: child_issue_number, - state: "closed", - state_reason: "completed" - }); - await github.rest.issues.createComment({ - owner, - repo, - issue_number: child_issue_number, - body: "Auto-close: BET-88 QA gate transitioned to PASS on #428." - }); - name: Open or reuse follow-up issue on FAIL if: steps.detect.outputs.status == 'fail' @@ -88,7 +75,6 @@ jobs: with: script: | const issue_number = context.payload.issue.number; - const child_issue_number = 456; const owner = context.repo.owner; const repo = context.repo.repo; const failComment = context.payload.comment?.html_url ?? ""; @@ -128,16 +114,3 @@ jobs: issue_number, body: `Auto-follow-up: detected top-level FAIL. Tracking fix in #${followup.number}.` }); - await github.rest.issues.update({ - owner, - repo, - issue_number: child_issue_number, - state: "closed", - state_reason: "completed" - }); - await github.rest.issues.createComment({ - owner, - repo, - issue_number: child_issue_number, - body: `Auto-close: BET-88 QA gate transitioned to FAIL on #428. Follow-up is #${followup.number}.` - }); diff --git a/.github/workflows/repo-hygiene.yml b/.github/workflows/repo-hygiene.yml new file mode 100644 index 00000000..0d202d22 --- /dev/null +++ b/.github/workflows/repo-hygiene.yml @@ -0,0 +1,51 @@ +name: Repo Hygiene + +on: + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + repo-hygiene: + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Agent preflight + shell: bash + run: bash scripts/dev/agent-preflight.sh origin/main + + - name: Shell syntax + shell: bash + run: | + set -euo pipefail + bash -n build-deps.sh + bash -n build-beta.sh + bash -n build.sh + bash -n run-tests.sh + bash -n run-integration-smoke.sh + bash -n run-e2e-smoke.sh + bash -n run-live-capture-smoke.sh + bash -n run-daily-audio-reliability.sh + find scripts -name '*.sh' -print0 | xargs -0 -n1 bash -n + + - name: Ruby syntax + shell: bash + run: ruby -c scripts/ops/agent-todo-runner.rb + + - name: Python syntax + shell: bash + run: | + set -euo pipefail + python3 -m py_compile scripts/ops/validate-meeting-corpus.py + python3 -m py_compile scripts/ops/compare-meeting-corpus.py + python3 -m py_compile scripts/ops/nightly-security-check.py + python3 -m py_compile scripts/ops/generate-nightly-digest.py + python3 -m py_compile scripts/ops/build-codex-memory-index.py diff --git a/AGENTS.md b/AGENTS.md index 009cdc02..0a48dc22 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -60,7 +60,7 @@ file taking precedence when there is any conflict. ```bash bash build-deps.sh -bash build.sh +bash build.sh --no-open bash run-tests.sh bash run-integration-smoke.sh swift test @@ -68,7 +68,7 @@ swift test Rules: -1. After changing Swift source, run `bash build.sh` and `bash run-tests.sh`. +1. After changing Swift source, run `bash build.sh --no-open` and `bash run-tests.sh`. 2. If you touch `Sources/Meeting/` or `Sources/TranscriptedCore/`, also run `bash run-integration-smoke.sh`. 3. If you touch `Package.swift`, `Sources/TranscriptedCore/`, or the public core seam, also run `swift test`. 4. `build.sh` must not compile `Sources/TranscriptedCore/` directly into the app target. @@ -100,7 +100,7 @@ Rules: - any automatic-check / automatic-download flags 9. Preferred release verification for release-path changes: - `bash build-deps.sh --force` when dependency tooling changes - - `bash build.sh` + - `bash build.sh --no-open` - `bash run-tests.sh` - `SKIP_NOTARIZATION=1 bash build-beta.sh ` for packaging smoke, or the full notarized path when cutting a real release @@ -141,7 +141,7 @@ Rules: - Settings should still expose the anonymous analytics toggle - Settings should still expose the `Send Test Sentry Event` action when Sentry is configured 12. Preferred verification for observability-related changes: - - `bash build.sh` + - `bash build.sh --no-open` - `bash run-tests.sh` - confirm Sentry, analytics, and observability preference tests still pass through `run-tests.sh` diff --git a/AGENT_START.md b/AGENT_START.md index cd9f7973..7f0cb02d 100644 --- a/AGENT_START.md +++ b/AGENT_START.md @@ -46,7 +46,7 @@ checks for the files changed. Default rules: -- Swift app change: `bash build.sh` and `bash run-tests.sh` +- Swift app change: `bash build.sh --no-open` and `bash run-tests.sh` - `Sources/Meeting/` or `Sources/TranscriptedCore/`: also `bash run-integration-smoke.sh` - `Package.swift` or public core seam: also `swift test` - release/update path: read `docs/release-packaging.md` and `docs/sparkle-updates.md` diff --git a/CLAUDE.md b/CLAUDE.md index 32399604..36d8bb24 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -28,7 +28,7 @@ Before making assumptions about the codebase: 6. `Sources/CLAUDE.md` — app target orientation 7. the nearest local `CLAUDE.md` for the area you are changing (see "Subsystem docs" below) -When docs disagree, prefer: current repo-level docs → current source → local `CLAUDE.md` whose file lists still match the tree → `docs/archive/` only as context. +When docs disagree, split the decision: `AGENTS.md` and `.agents/test-matrix.yml` win for workflow contracts; current source wins for runtime behavior and file existence; local `CLAUDE.md` files explain subsystem intent when their file lists still match the tree; `docs/archive/` is context only. ## Build and test @@ -36,7 +36,7 @@ Common commands (thin root wrappers; implementations live under `scripts/entrypo ```bash bash build-deps.sh # build/refresh prebuilt deps under deps-libs/, deps-frameworks/, deps-modules/ -bash build.sh # authoritative app build (raw swiftc, NOT swift build) +bash build.sh --no-open # authoritative app build for non-interactive verification bash run-tests.sh # curated fast tests (manifest-driven) bash run-integration-smoke.sh # app/core linkage + wake recovery + MicRecordingFileMerger bash run-e2e-smoke.sh # deterministic release-critical artifact smoke (no mic/TCC) @@ -48,18 +48,18 @@ bash scripts/dev/agent-preflight.sh # prints suggested verification map for the Verification rules (mirror `.agents/test-matrix.yml`; if a change matches multiple rules, run the union): -- Touched `Sources/**/*.swift`, root `Tests/*.swift`, or `Tests/FastTests.manifest` → `bash build.sh` + `bash run-tests.sh` -- Touched `Sources/Meeting/**`, `Sources/TranscriptedCore/**`, or `Tests/Integration/**` → `bash build.sh` + `bash run-tests.sh` + `bash run-integration-smoke.sh` +- Touched `Sources/**/*.swift`, root `Tests/*.swift`, or `Tests/FastTests.manifest` → `bash build.sh --no-open` + `bash run-tests.sh` +- Touched `Sources/Meeting/**`, `Sources/TranscriptedCore/**`, or `Tests/Integration/**` → `bash build.sh --no-open` + `bash run-tests.sh` + `bash run-integration-smoke.sh` - Touched `Tests/E2E/**`, `run-e2e-smoke.sh`, or `scripts/entrypoints/run-e2e-smoke.sh` → `bash run-e2e-smoke.sh` - Touched QA bench/corpus files (`scripts/ops/transcripted-qa-bench.sh`, `scripts/ops/validate-meeting-corpus.py`, `scripts/ops/compare-meeting-corpus.py`, `docs/qa-test-bench.md`) → quick QA bench + Python compile checks - Touched live-capture smoke paths (`Tests/TranscriptedCoreTests/LiveCaptureSmokeTests.swift`, `run-live-capture-smoke.sh`, `scripts/entrypoints/run-live-capture-smoke.sh`) → `bash run-live-capture-smoke.sh --skip-build` -- Touched `Package.swift`, `Sources/TranscriptedCore/**`, or `Tests/TranscriptedCoreTests/**` → `bash build.sh` + `bash run-tests.sh` + `bash run-integration-smoke.sh` + `swift test` -- Touched `Sources/Observability/**`, `Info.plist`, `docs/sparkle-updates.md`, or `docs/appcast.xml` → `bash build.sh` + `bash run-tests.sh` -- Touched release path (`build-beta.sh`, `scripts/entrypoints/build-beta.sh`, `scripts/release/**`, `docs/release-packaging.md`, `docs/sparkle-updates.md`, `Casks/**`, `docs/appcast.xml`) → `bash build.sh` + `bash run-tests.sh` + `SKIP_NOTARIZATION=1 bash build-beta.sh ` +- Touched `Package.swift`, `Sources/TranscriptedCore/**`, or `Tests/TranscriptedCoreTests/**` → `bash build.sh --no-open` + `bash run-tests.sh` + `bash run-integration-smoke.sh` + `swift test` +- Touched `Sources/Observability/**`, `Info.plist`, `docs/sparkle-updates.md`, or `docs/appcast.xml` → `bash build.sh --no-open` + `bash run-tests.sh` +- Touched release path (`build-beta.sh`, `scripts/entrypoints/build-beta.sh`, `scripts/release/**`, `docs/release-packaging.md`, `docs/sparkle-updates.md`, `Casks/**`, `docs/appcast.xml`) → `bash build.sh --no-open` + `bash run-tests.sh` + `SKIP_NOTARIZATION=1 bash build-beta.sh ` - Touched `Tools/TranscriptedCLI/**` → `swift test --package-path Tools/TranscriptedCLI` - Touched `Tools/TranscriptedMCP/**` → `swift test --package-path Tools/TranscriptedMCP` - Touched `Tools/TranscriptedQA/**` → `swift test --package-path Tools/TranscriptedQA` -- Touched docs/agent files (`README.md`, `AGENT_START.md`, `AGENTS.md`, `CLAUDE.md`, `CONTRIBUTING.md`, `docs/**`, `.agents/**`) → `scripts/dev/agent-preflight.sh` +- Touched docs/agent files (`README.md`, `AGENT_START.md`, `AGENTS.md`, `CLAUDE.md`, `CONTRIBUTING.md`, `WORKFLOW.md`, `docs/**`, `.agents/**`, `.github/**`) → `scripts/dev/agent-preflight.sh` ### Fast-test gotchas diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 20bf5747..dfe7a62e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -37,7 +37,7 @@ state already exists. ```bash bash build-deps.sh - bash build.sh + bash build.sh --no-open ``` 3. Run the test suite: @@ -156,9 +156,9 @@ If you're changing meeting integration or `TranscriptedCore`, also run: bash run-integration-smoke.sh ``` -`run-tests.sh` is curated rather than discovery-based. If you add a new test -file or move a source file that the test script compiles directly, update -`run-tests.sh` in the same change. +`run-tests.sh` is curated rather than discovery-based. If you add a new root +fast-test file, register it in `Tests/FastTests.manifest`. Update +`run-tests.sh` only when the compiled source list or runner behavior changes. ## Submitting a Pull Request diff --git a/README.md b/README.md index 346a29fd..4637a44e 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ later. ## Demo -![Transcripted quick tour showing Home, Meetings, Dictation, and Home again](docs/assets/launch/transcripted-demo.gif) +![Transcripted quick tour showing the Home dashboard, meeting capture, dictation, and recent activity](docs/assets/launch/transcripted-demo.gif) ![Transcripted dictation recording showing the listening control](docs/assets/launch/transcripted-dictation-recording.gif) @@ -224,7 +224,7 @@ For contributors: ```bash bash build-deps.sh -bash build.sh +bash build.sh --no-open ``` `build.sh` is the main app build. `Package.swift` exists for diff --git a/Sources/Accessibility/AccessibilityBridge.swift b/Sources/Accessibility/AccessibilityBridge.swift index 68d665f8..2d5d6e1b 100644 --- a/Sources/Accessibility/AccessibilityBridge.swift +++ b/Sources/Accessibility/AccessibilityBridge.swift @@ -21,8 +21,7 @@ struct AccessibilityBridge { let result = AXUIElementCopyAttributeValue(appElement, kAXFocusedUIElementAttribute as CFString, &focusedRef) guard result == .success, let focusedElement = focusedRef else { return nil } - guard CFGetTypeID(focusedElement) == AXUIElementGetTypeID() else { return nil } - let axElement = focusedElement as! AXUIElement + guard let axElement = axElement(from: focusedElement) else { return nil } var roleRef: AnyObject? AXUIElementCopyAttributeValue(axElement, kAXRoleAttribute as CFString, &roleRef) @@ -60,17 +59,27 @@ struct AccessibilityBridge { AXUIElementCopyAttributeValue(axElement, kAXPositionAttribute as CFString, &posRef) AXUIElementCopyAttributeValue(axElement, kAXSizeAttribute as CFString, &sizeRef) - guard let posValue = posRef, let sizeValue = sizeRef, - CFGetTypeID(posValue) == AXValueGetTypeID(), - CFGetTypeID(sizeValue) == AXValueGetTypeID() else { return nil } + guard let posValue = axValue(from: posRef), + let sizeValue = axValue(from: sizeRef) else { return nil } var point = CGPoint.zero var size = CGSize.zero - guard AXValueGetValue(posValue as! AXValue, .cgPoint, &point), - AXValueGetValue(sizeValue as! AXValue, .cgSize, &size) else { + guard AXValueGetValue(posValue, .cgPoint, &point), + AXValueGetValue(sizeValue, .cgSize, &size) else { return nil } return CGRect(origin: point, size: size) } + + private static func axElement(from value: AnyObject) -> AXUIElement? { + guard CFGetTypeID(value) == AXUIElementGetTypeID() else { return nil } + return unsafeBitCast(value, to: AXUIElement.self) + } + + private static func axValue(from value: AnyObject?) -> AXValue? { + guard let value, + CFGetTypeID(value) == AXValueGetTypeID() else { return nil } + return unsafeBitCast(value, to: AXValue.self) + } } diff --git a/Sources/Speech/STTRouter.swift b/Sources/Speech/STTRouter.swift index c8f60088..dd6dfd6d 100644 --- a/Sources/Speech/STTRouter.swift +++ b/Sources/Speech/STTRouter.swift @@ -3,7 +3,7 @@ import Combine import FluidAudio -import SwiftUI +import Foundation @MainActor class STTRouter: ObservableObject { diff --git a/Sources/TranscriptedCore/Audio/Audio.swift b/Sources/TranscriptedCore/Audio/Audio.swift index 69f96ff6..4a94ce00 100644 --- a/Sources/TranscriptedCore/Audio/Audio.swift +++ b/Sources/TranscriptedCore/Audio/Audio.swift @@ -771,7 +771,7 @@ public class Audio: ObservableObject, @unchecked Sendable { } // Pre-flight validation checks - let validationResult = RecordingValidator.validateRecordingConditions() + let validationResult = RecordingValidator.validateRecordingConditions(paths: paths) guard validationResult.isValid else { AppLogger.audio.error("Pre-flight check failed", ["error": validationResult.errorMessage ?? "Unknown error"]) error = validationResult.errorMessage diff --git a/Sources/TranscriptedCore/Storage/TranscriptFrontmatter.swift b/Sources/TranscriptedCore/Storage/TranscriptFrontmatter.swift index 11359572..0783cf84 100644 --- a/Sources/TranscriptedCore/Storage/TranscriptFrontmatter.swift +++ b/Sources/TranscriptedCore/Storage/TranscriptFrontmatter.swift @@ -89,7 +89,7 @@ public enum TranscriptFrontmatter { let handle = try FileHandle(forReadingFrom: url) defer { try? handle.close() } - let readLimit = max(byteLimit, maximumFrontmatterByteLimit) + let readLimit = maximumFrontmatterByteLimit let chunkSize = max(1, min(byteLimit, previewByteLimit)) var data = Data() diff --git a/Sources/UI/CLAUDE.md b/Sources/UI/CLAUDE.md index 3a89dd87..56129c62 100644 --- a/Sources/UI/CLAUDE.md +++ b/Sources/UI/CLAUDE.md @@ -89,7 +89,7 @@ The current agent-connect surfaces should keep one simple mental model: - `Settings/TranscriptedSettingsActions.swift` — struct of callbacks (start dictation, start meeting, import audio, paste, connect agent, check updates, send feedback, copy/send diagnostics) injected into the settings view - `Settings/TranscriptedSettingsComponents.swift` — shared SwiftUI building blocks (`SettingsPageIntro`, `SettingsSection`) used across settings pages - `Settings/TranscriptedSettingsNavigationModel.swift` — observable navigation state for the current `TranscriptedSettingsPage` selection -- `Settings/TranscriptedSettingsPage.swift` — enum of settings pages (home, general, models, shortcuts, dictations, people, storage, connectAgent, privacy, support, about) with titles, summaries, and SF Symbol names +- `Settings/TranscriptedSettingsPage.swift` — enum of settings pages (home, general, models, shortcuts, people, storage, connectAgent, privacy, support, about) with titles, summaries, and SF Symbol names - `Settings/TranscriptedSettingsView.swift` — main settings view - `Settings/TranscriptedSettingsWindowController.swift` — NSWindowController for settings diff --git a/Tests/README.md b/Tests/README.md index 7d091843..8aaa10e5 100644 --- a/Tests/README.md +++ b/Tests/README.md @@ -12,7 +12,7 @@ This repo has six distinct verification layers: Deterministic release-critical artifact smoke without microphone/TCC 4. `swift test` Swift Package tests for the standalone `TranscriptedCore` package surface -5. `bash build.sh` +5. `bash build.sh --no-open` Authoritative app build for the menubar target 6. `bash run-live-capture-smoke.sh` Local hardware/TCC smoke for app launch plus production mic + system-audio capture diff --git a/Tests/RepoCommandContractTests.swift b/Tests/RepoCommandContractTests.swift index b063be8c..b6121d71 100644 --- a/Tests/RepoCommandContractTests.swift +++ b/Tests/RepoCommandContractTests.swift @@ -133,6 +133,71 @@ func testRepoCommandContract() { contents.contains("trap cleanup_generated_runner EXIT"), "temporary generated test runners should be removed on exit" ) + assertTrue( + contents.contains("Unknown option: $arg") && contents.contains("exit 2"), + "run-tests.sh should reject unknown flags instead of silently ignoring typos" + ) + } + + runSuite("Repo command contract - agent verification stays non-interactive") { + let matrix = readRepoTextFile(".agents/test-matrix.yml") + let preflight = readRepoTextFile("scripts/dev/agent-preflight.sh") + let agents = readRepoTextFile("AGENTS.md") + let agentStart = readRepoTextFile("AGENT_START.md") + let workflow = readRepoTextFile("WORKFLOW.md") + + assertTrue( + matrix.contains("bash build.sh --no-open") + && !matrix.contains("\"bash build.sh\""), + "agent test matrix should use the non-opening build command" + ) + assertTrue( + preflight.contains("add_command \"bash build.sh --no-open\"") + && !preflight.contains("add_command \"bash build.sh\""), + "agent preflight should suggest the non-opening build command" + ) + assertTrue( + agents.contains("bash build.sh --no-open") + && agentStart.contains("bash build.sh --no-open") + && workflow.contains(".agents/test-matrix.yml"), + "agent-facing docs should keep verification non-interactive and defer unattended work to the matrix" + ) + } + + runSuite("Repo command contract - script edits map to syntax and owned checks") { + let matrix = readRepoTextFile(".agents/test-matrix.yml") + let preflight = readRepoTextFile("scripts/dev/agent-preflight.sh") + let expectedChecks = [ + "bash -n scripts/entrypoints/build-deps.sh", + "bash build-deps.sh --force", + "bash -n scripts/entrypoints/build.sh", + "bash -n scripts/entrypoints/run-tests.sh", + "bash -n scripts/entrypoints/run-integration-smoke.sh", + "bash -n scripts/ops/daily-audio-reliability-check.sh" + ] + + for check in expectedChecks { + assertTrue(matrix.contains(check), "test matrix should include \(check)") + assertTrue(preflight.contains(check), "agent preflight should include \(check)") + } + } + + runSuite("Repo command contract - integration smoke rejects stale Core deps") { + let contents = readRepoTextFile("scripts/entrypoints/run-integration-smoke.sh") + assertTrue( + contents.contains("newest_dependency_input") + && contents.contains("deps_build_stamp_info") + && contents.contains("Dependencies are stale for TranscriptedCore."), + "integration smoke should refuse stale TranscriptedCore dependency artifacts" + ) + } + + runSuite("Repo command contract - Audio preflight uses injected Core paths") { + let contents = readRepoTextFile("Sources/TranscriptedCore/Audio/Audio.swift") + assertTrue( + contents.contains("RecordingValidator.validateRecordingConditions(paths: paths)"), + "Audio.start should validate the same CoreStoragePaths that the embedder injected" + ) } runSuite("Repo command contract - deterministic E2E smoke stays on the release surface") { @@ -1139,6 +1204,63 @@ func testRepoCommandContract() { ) } + runSuite("Repo command contract - GitHub agent surfaces stay preflight-visible") { + let matrix = readRepoTextFile(".agents/test-matrix.yml") + let preflight = readRepoTextFile("scripts/dev/agent-preflight.sh") + let agentTemplate = readRepoTextFile(".github/ISSUE_TEMPLATE/agent_task.md") + let bugTemplate = readRepoTextFile(".github/ISSUE_TEMPLATE/bug_report.md") + let featureTemplate = readRepoTextFile(".github/ISSUE_TEMPLATE/feature_request.md") + let prTemplate = readRepoTextFile(".github/PULL_REQUEST_TEMPLATE.md") + let repoHygieneWorkflow = readRepoTextFile(".github/workflows/repo-hygiene.yml") + let qaGateAutoClose = readRepoTextFile(".github/workflows/qa-gate-auto-close-bet88.yml") + + assertTrue( + matrix.contains("\".github/**\"") + && matrix.contains("\"WORKFLOW.md\"") + && matrix.contains("\"scripts/dev/agent-preflight.sh\"") + && matrix.contains("ruby -c scripts/ops/agent-todo-runner.rb") + && matrix.contains("bash -n scripts/ops/qa-gate-check.sh"), + "test matrix should treat GitHub templates, workflow contract, and preflight script as agent-contract surfaces" + ) + assertTrue( + preflight.contains("\".github/*\"") + && preflight.contains("\"WORKFLOW.md\"") + && preflight.contains("\"scripts/dev/agent-preflight.sh\"") + && preflight.contains("ruby -c scripts/ops/agent-todo-runner.rb") + && preflight.contains("bash -n scripts/ops/qa-gate-check.sh"), + "agent preflight should suggest itself when GitHub templates or workflow docs change" + ) + assertTrue( + agentTemplate.contains("does not start the local runner by itself") + && agentTemplate.contains("`agent todo` label"), + "agent issue template should make the manual queue label explicit" + ) + assertTrue( + bugTemplate.contains("Please redact transcripts, audio, meeting titles, speaker names, emails, tokens") + && featureTemplate.contains("Please do not include private transcripts, audio, meeting titles, speaker names"), + "public issue templates should put privacy redaction guidance where users attach diagnostics" + ) + assertTrue( + prTemplate.contains("`scripts/dev/agent-preflight.sh`") + && prTemplate.contains("`swift test` if I touched `Package.swift`, `Sources/TranscriptedCore/`, or the public core seam") + && prTemplate.contains("Agent PRs link the issue/workpad") + && prTemplate.contains("No private transcripts, audio, tokens, personal paths, or customer data"), + "PR template should point reviewers at preflight, core package checks, agent review evidence, and privacy review" + ) + assertTrue( + repoHygieneWorkflow.contains("on:\n pull_request:") + && repoHygieneWorkflow.contains("bash scripts/dev/agent-preflight.sh origin/main") + && repoHygieneWorkflow.contains("ruby -c scripts/ops/agent-todo-runner.rb") + && repoHygieneWorkflow.contains("python3 -m py_compile scripts/ops/nightly-security-check.py"), + "repo should have a lightweight pull_request hygiene workflow for repo plumbing" + ) + assertTrue( + qaGateAutoClose.contains("contains(github.event.issue.labels.*.name, 'qa-gate-auto-close')") + && !qaGateAutoClose.contains("child_issue_number"), + "old BET-88 auto-close workflow should be label-gated and should not mutate a hard-coded child issue" + ) + } + runSuite("Repo command contract - onboarding agent copy remains measurable") { let contents = readRepoTextFile("Sources/UI/Settings/PermissionsOnboardingView.swift") assertTrue( diff --git a/Tests/TranscriptedCoreTests/CoreStoragePathsTests.swift b/Tests/TranscriptedCoreTests/CoreStoragePathsTests.swift index 2a0019b2..2d07b908 100644 --- a/Tests/TranscriptedCoreTests/CoreStoragePathsTests.swift +++ b/Tests/TranscriptedCoreTests/CoreStoragePathsTests.swift @@ -1,10 +1,7 @@ import XCTest @testable import TranscriptedCore -/// Smoke tests for the public Core API — verifies that `import TranscriptedCore` -/// produces a linkable test binary and that the Step 7 / Step 8 seams work as -/// documented. Heavier logic-level tests still live in the app-target -/// `TranscriptedTests/` directory until Step 10 rewires the Xcode app target. +/// Storage-path and recording-validator coverage for the Core package seam. @available(macOS 14.0, *) final class CoreStoragePathsTests: XCTestCase { diff --git a/Tests/TranscriptedCoreTests/PublicTranscriptedCoreAPITests.swift b/Tests/TranscriptedCoreTests/PublicTranscriptedCoreAPITests.swift new file mode 100644 index 00000000..72e45ff9 --- /dev/null +++ b/Tests/TranscriptedCoreTests/PublicTranscriptedCoreAPITests.swift @@ -0,0 +1,24 @@ +import XCTest +import TranscriptedCore + +@available(macOS 14.0, *) +final class PublicTranscriptedCoreAPITests: XCTestCase { + + func testPublicCoreStoragePathsAndRecordingValidatorAreImportable() { + let root = FileManager.default.temporaryDirectory + .appendingPathComponent("PublicTranscriptedCoreAPITests-\(UUID().uuidString)", isDirectory: true) + let paths = CoreStoragePaths( + transcripts: root.appendingPathComponent("meetings", isDirectory: true), + speakerDB: root.appendingPathComponent("speakers.sqlite"), + statsDB: root.appendingPathComponent("stats.sqlite"), + failedQueue: root.appendingPathComponent("failed.json"), + speakerClips: root.appendingPathComponent("clips", isDirectory: true), + audioCaptures: root.appendingPathComponent("recordings", isDirectory: true), + logs: root.appendingPathComponent("logs", isDirectory: true) + ) + + XCTAssertEqual(paths.transcripts.lastPathComponent, "meetings") + XCTAssertEqual(RecordingValidator.minimumDiskSpace, 100 * 1024 * 1024) + XCTAssertTrue(RecordingValidator.validateSavePath(paths.transcripts).isValid) + } +} diff --git a/Tests/TranscriptedCoreTests/TranscriptFrontmatterTests.swift b/Tests/TranscriptedCoreTests/TranscriptFrontmatterTests.swift index d50ac119..3f2a7c0e 100644 --- a/Tests/TranscriptedCoreTests/TranscriptFrontmatterTests.swift +++ b/Tests/TranscriptedCoreTests/TranscriptFrontmatterTests.swift @@ -115,4 +115,29 @@ final class TranscriptFrontmatterTests: XCTestCase { XCTAssertEqual(values["title"], "Long Meeting") XCTAssertEqual(values["duration"], "120:00") } + + func testReadDocumentClampsOversizedByteLimitToMaximumFrontmatterLimit() throws { + let directory = FileManager.default.temporaryDirectory + .appendingPathComponent("TranscriptFrontmatterTests-\(UUID().uuidString)", isDirectory: true) + try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) + defer { try? FileManager.default.removeItem(at: directory) } + + let url = directory.appendingPathComponent("oversized-frontmatter.md") + let raw = """ + --- + title: "Oversized" + padding: "\(String(repeating: "x", count: TranscriptFrontmatter.maximumFrontmatterByteLimit + 8_192))" + --- + + Body + """ + try raw.write(to: url, atomically: true, encoding: .utf8) + + let document = try TranscriptFrontmatter.readDocument( + from: url, + byteLimit: TranscriptFrontmatter.maximumFrontmatterByteLimit * 2 + ) + + XCTAssertNil(document) + } } diff --git a/WORKFLOW.md b/WORKFLOW.md index e9ce77be..9553d9b6 100644 --- a/WORKFLOW.md +++ b/WORKFLOW.md @@ -55,7 +55,7 @@ Use the issue as the source of truth. If the issue is too vague to implement saf 7. Create or reuse a branch named like `codex/issue-{{ issue.number }}-short-slug`. 8. If a draft PR already exists for this issue or branch, update that PR instead of opening a duplicate. 9. Make the smallest change that satisfies the issue. -10. Run the verification required by the files you changed. +10. Run `scripts/dev/agent-preflight.sh`, then run the union of checks required by `.agents/test-matrix.yml` for the files you changed. 11. Stage only your own changes, commit, and push. 12. If the change touches UI, visual design, app copy, or user-facing flows, add sanitized visual evidence under `.agent-review/visuals/` before opening or updating the PR. Prefer a PNG screenshot; use a GIF only when motion or interaction matters. Never include private transcripts, customer data, tokens, absolute personal paths, or real user content in visuals. 13. Open a draft PR against `main` with `gh pr create --draft`, unless you are updating an existing PR. @@ -70,9 +70,7 @@ Use the issue as the source of truth. If the issue is too vague to implement saf - Never stage unrelated pre-existing changes. - Never edit files outside this workspace. - Preserve Transcripted privacy boundaries. -- If you touch Swift source, run `bash build.sh` and `bash run-tests.sh`. -- If you touch `Sources/Meeting/` or `Sources/TranscriptedCore/`, also run `bash run-integration-smoke.sh`. -- If you touch `Package.swift`, `Sources/TranscriptedCore/`, or the public core seam, also run `swift test`. +- Verification examples: Swift source usually needs `bash build.sh --no-open` and `bash run-tests.sh`; `Sources/Meeting/` or `Sources/TranscriptedCore/` also need `bash run-integration-smoke.sh`; package/core seam changes also need `swift test`. - For UI changes, make the PR reviewable without pulling the branch locally: include a screenshot or GIF in `.agent-review/visuals/` and note the manual check in the PR body. - For review follow-up, do not close the issue or create a new issue. Update the same branch and PR, then put the issue back in `human review`. diff --git a/docs/agent-onboarding.md b/docs/agent-onboarding.md index 4b0f1467..95f10ff5 100644 --- a/docs/agent-onboarding.md +++ b/docs/agent-onboarding.md @@ -56,6 +56,8 @@ For the active directory map and command surface, prefer `docs/repo-layout.md`. Verification surfaces and fast-test runner rules. - `.agents/test-matrix.yml` Machine-readable path-to-verification map for agents. +- `.github/` + GitHub issue templates, PR checklist, and workflow automation. - `docs/storage-paths.md` Canonical app, tool, and fallback storage layout. - `docs/docs.md` @@ -67,31 +69,38 @@ For the active directory map and command surface, prefer `docs/repo-layout.md`. ## What To Trust Most -When docs disagree, prefer: +When docs disagree, split the decision: -1. current repo-level docs -2. current source files -3. current local `CLAUDE.md` files whose file lists match the tree -4. `docs/archive/` only as context +- workflow contracts: `AGENTS.md`, `.agents/test-matrix.yml`, then `scripts/dev/agent-preflight.sh` +- runtime behavior and file existence: current source files +- subsystem intent: current local `CLAUDE.md` files whose file lists match the tree +- historical context: `docs/archive/` only ## Validation Layers -This repo has four different verification surfaces that agents should treat as -distinct: +This repo has multiple verification surfaces. Treat them as distinct, and use +`Tests/README.md` plus `.agents/test-matrix.yml` as the full current map: - `bash build.sh` - The authoritative app build. + The authoritative app build. Use `bash build.sh --no-open` for agent verification. - `bash run-tests.sh` Curated fast tests for app-facing logic. - `bash run-integration-smoke.sh` App/Core integration smoke. +- `bash run-e2e-smoke.sh` + Deterministic release-critical artifact smoke. - `swift test` SPM tests for `TranscriptedCore`. +- `bash run-live-capture-smoke.sh` + Local hardware/TCC smoke for app launch plus production mic and system-audio capture. +- `bash scripts/ops/transcripted-qa-bench.sh --mode quick` + Orchestrated QA bench for broader local validation. Rule of thumb: - run `scripts/dev/agent-preflight.sh` when starting or handing off a branch -- after Swift edits, run `bash build.sh` and `bash run-tests.sh` +- follow the union of checks from `.agents/test-matrix.yml` +- after Swift edits, run `bash build.sh --no-open` and `bash run-tests.sh` - if you touch `Sources/Meeting/` or `Sources/TranscriptedCore/`, also run `bash run-integration-smoke.sh` - if you touch `Package.swift`, `Sources/TranscriptedCore/`, or the public diff --git a/docs/archive/merge/merge-plan.md b/docs/archive/merge/merge-plan.md index cf44200a..1baeea94 100644 --- a/docs/archive/merge/merge-plan.md +++ b/docs/archive/merge/merge-plan.md @@ -1,5 +1,7 @@ # Draft + Transcripted Merge Plan (Phase 0 Deliverable) +> Archived historical plan. Do not execute this as current guidance. + **Authors:** draft-mapper (owner), transcripted-mapper (contributor) **Status:** v6 for human review — end of Phase 0. Phase 2 execution starts only after human sign-off. **Inputs:** [draft-inventory.md](draft-inventory.md), [transcripted-inventory.md](transcripted-inventory.md) diff --git a/docs/audio-reliability-daily-check.md b/docs/audio-reliability-daily-check.md index 4d2ce0d6..8cb0f18e 100644 --- a/docs/audio-reliability-daily-check.md +++ b/docs/audio-reliability-daily-check.md @@ -136,7 +136,7 @@ Use the failed scenario folder as the before-run, patch narrowly, then rerun the same scenario. After a fix, run: ```bash -bash build.sh +bash build.sh --no-open bash run-tests.sh bash run-integration-smoke.sh ``` diff --git a/docs/privacy-first-observability.md b/docs/privacy-first-observability.md index 65029586..59303014 100644 --- a/docs/privacy-first-observability.md +++ b/docs/privacy-first-observability.md @@ -59,7 +59,7 @@ references, meeting titles, speaker names, local paths, or user identifiers. have `~/Library/Application Support/Draft/observability-overrides.plist`. 5. Run `bash build-deps.sh --force` once to download the pinned Sentry and Sparkle frameworks. -6. Run `bash build.sh` and `bash run-tests.sh`. +6. Run `bash build.sh --no-open` and `bash run-tests.sh`. 7. In the app, verify onboarding shows two separate default-on questions for: - crash and error reports - anonymous usage statistics diff --git a/docs/qa-issue-500-meeting-audio.md b/docs/qa-issue-500-meeting-audio.md index f5c5b958..595b3368 100644 --- a/docs/qa-issue-500-meeting-audio.md +++ b/docs/qa-issue-500-meeting-audio.md @@ -13,7 +13,7 @@ The goal is to prove three things: Build and launch the app you are testing: ```bash -bash build.sh +bash build.sh --no-open open build/Transcripted.app ``` diff --git a/docs/qa-parakeet-start-failure-smoke.md b/docs/qa-parakeet-start-failure-smoke.md index 2e75316f..623af4be 100644 --- a/docs/qa-parakeet-start-failure-smoke.md +++ b/docs/qa-parakeet-start-failure-smoke.md @@ -74,8 +74,10 @@ Expected: ## QA Result Comment Format (for `#428`) -The BET-88 gate automation reads the first non-empty line of your top-level -comment on `#428`. +The manual BET-88 gate helper reads the first non-empty line of your top-level +comment on `#428`. The GitHub auto-close workflow is additionally gated by the +`qa-gate-auto-close` label so old issue comments cannot mutate closed issues by +accident. Use one of these exact first-line forms: diff --git a/docs/release-packaging.md b/docs/release-packaging.md index 290258e4..faff9efe 100644 --- a/docs/release-packaging.md +++ b/docs/release-packaging.md @@ -76,7 +76,7 @@ xcrun notarytool store-credentials ... To force a specific certificate for either build flow: ```bash -SIGN_IDENTITY= bash build.sh +SIGN_IDENTITY= bash build.sh --no-open SIGNING_IDENTITY= bash build-beta.sh ``` diff --git a/docs/repo-layout.md b/docs/repo-layout.md index a877b9dd..cdb4f5b0 100644 --- a/docs/repo-layout.md +++ b/docs/repo-layout.md @@ -24,7 +24,7 @@ Use these as the active command surface: ```bash bash scripts/dev/agent-preflight.sh bash build-deps.sh -bash build.sh +bash build.sh --no-open bash run-tests.sh bash run-integration-smoke.sh bash run-e2e-smoke.sh @@ -40,7 +40,7 @@ Command ownership: - `scripts/dev/agent-preflight.sh` — agent preflight and suggested verification map for the current branch - `build-deps.sh` — thin root wrapper for the dependency build entrypoint -- `build.sh` — thin root wrapper for the authoritative local app build +- `build.sh` — thin root wrapper for the authoritative local app build; use `--no-open` for agent verification - `build-beta.sh` — thin root wrapper for signed beta/distribution builds - `run-tests.sh` — thin root wrapper for curated fast tests - `run-integration-smoke.sh` — thin root wrapper for app/core smoke verification @@ -58,6 +58,7 @@ For helper and legacy scripts, see `scripts/README.md`. - `.agents/` — machine-readable agent maps, currently the path-to-verification matrix - `.agent-review/` — sanitized review evidence for agent PRs, not current UI truth +- `.github/` — issue templates, PR template, and repository workflows - `Sources/` — macOS app target - `Sources/Accessibility/` — AX helpers for overlay positioning - `Sources/Beta/` — beta-only configuration @@ -88,6 +89,7 @@ Use these docs for these jobs: - `CONTRIBUTING.md` — contributor setup and contribution norms - `AGENTS.md` — Codex-specific workflow rules - `WORKFLOW.md` - local GitHub Issues to Codex agent workflow contract +- `.github/` — GitHub issue templates, PR checklist, and workflow automation - `CLAUDE.md` — Claude-specific repo orientation - `docs/agent-onboarding.md` — how to interpret the repo’s doc layers - `docs/docs.md` - documentation tone, drift checks, and follow-up PR rules diff --git a/scripts/README.md b/scripts/README.md index c1d528ab..24bbb59c 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -69,7 +69,7 @@ with the operational health probes at `scripts/ops/daily-audio-reliability-check - Usage: `bash scripts/ops/agent-todo-launchagent.sh install` - Usage: `bash scripts/ops/agent-todo-launchagent.sh status` - Usage: `bash scripts/ops/agent-todo-launchagent.sh logs` -- `scripts/ops/qa-gate-check.sh` — one-shot check for the BET-88 QA gate comment on `#428` using the same strict owner + first-line PASS/FAIL rules as the auto-close workflow +- `scripts/ops/qa-gate-check.sh` — one-shot check for the BET-88 QA gate comment on `#428` using the same strict owner + first-line PASS/FAIL rules as the label-gated auto-close workflow - Usage: `bash scripts/ops/qa-gate-check.sh [--json] [repo] [issue_number] [owner_login]` - Returns JSON and exits `0` for `pass`/`fail`, `3` for `PENDING` - `scripts/ops/qa-gate-closeout.sh` — closeout wrapper around `qa-gate-check.sh` that prints explicit unblock owner/action when status is still pending diff --git a/scripts/dev/agent-preflight.sh b/scripts/dev/agent-preflight.sh index cf726242..e839f134 100755 --- a/scripts/dev/agent-preflight.sh +++ b/scripts/dev/agent-preflight.sh @@ -80,17 +80,17 @@ matches_any() { if [ -n "$changed_paths" ]; then while IFS= read -r path; do if matches_any "$path" "Sources/*.swift" "Sources/*/*.swift" "Sources/*/*/*.swift" "Sources/*/*/*/*.swift" "Tests/*.swift" "Tests/*/*.swift" "Tests/*/*/*.swift" "Tests/FastTests.manifest"; then - add_command "bash build.sh" + add_command "bash build.sh --no-open" add_command "bash run-tests.sh" fi if matches_any "$path" "Info.plist"; then - add_command "bash build.sh" + add_command "bash build.sh --no-open" add_command "bash run-tests.sh" fi if matches_any "$path" "Sources/Meeting/*" "Sources/TranscriptedCore/*" "Tests/Integration/*"; then - add_command "bash build.sh" + add_command "bash build.sh --no-open" add_command "bash run-tests.sh" add_command "bash run-integration-smoke.sh" fi @@ -99,18 +99,60 @@ if [ -n "$changed_paths" ]; then add_command "bash run-e2e-smoke.sh" fi + if matches_any "$path" "build-deps.sh" "scripts/entrypoints/build-deps.sh"; then + add_command "scripts/dev/agent-preflight.sh" + add_command "bash -n build-deps.sh" + add_command "bash -n scripts/entrypoints/build-deps.sh" + add_command "bash build-deps.sh --force" + fi + + if matches_any "$path" "build.sh" "scripts/entrypoints/build.sh"; then + add_command "scripts/dev/agent-preflight.sh" + add_command "bash -n build.sh" + add_command "bash -n scripts/entrypoints/build.sh" + add_command "bash build.sh --no-open" + fi + + if matches_any "$path" "run-tests.sh" "scripts/entrypoints/run-tests.sh" "Tests/FastTests.manifest"; then + add_command "scripts/dev/agent-preflight.sh" + add_command "bash -n run-tests.sh" + add_command "bash -n scripts/entrypoints/run-tests.sh" + add_command "bash run-tests.sh" + fi + + if matches_any "$path" "run-integration-smoke.sh" "scripts/entrypoints/run-integration-smoke.sh" "Tests/Integration/*"; then + add_command "scripts/dev/agent-preflight.sh" + add_command "bash -n run-integration-smoke.sh" + add_command "bash -n scripts/entrypoints/run-integration-smoke.sh" + add_command "bash run-integration-smoke.sh" + fi + + if matches_any "$path" "run-daily-audio-reliability.sh" "scripts/ops/daily-audio-reliability-check.sh"; then + add_command "scripts/dev/agent-preflight.sh" + add_command "bash -n run-daily-audio-reliability.sh" + add_command "bash -n scripts/ops/daily-audio-reliability-check.sh" + fi + if matches_any "$path" "scripts/ops/transcripted-qa-bench.sh" "scripts/ops/validate-meeting-corpus.py" "scripts/ops/compare-meeting-corpus.py" "docs/qa-test-bench.md"; then add_command "bash scripts/ops/transcripted-qa-bench.sh --mode quick" add_command "python3 -m py_compile scripts/ops/validate-meeting-corpus.py" add_command "python3 -m py_compile scripts/ops/compare-meeting-corpus.py" fi + if matches_any "$path" "scripts/ops/agent-todo-runner.rb" "scripts/ops/agent-todo-launchagent.sh" "scripts/ops/qa-gate-check.sh" "scripts/ops/qa-gate-closeout.sh"; then + add_command "scripts/dev/agent-preflight.sh" + add_command "ruby -c scripts/ops/agent-todo-runner.rb" + add_command "bash -n scripts/ops/agent-todo-launchagent.sh" + add_command "bash -n scripts/ops/qa-gate-check.sh" + add_command "bash -n scripts/ops/qa-gate-closeout.sh" + fi + if matches_any "$path" "Tests/TranscriptedCoreTests/LiveCaptureSmokeTests.swift" "run-live-capture-smoke.sh" "scripts/entrypoints/run-live-capture-smoke.sh"; then add_command "bash run-live-capture-smoke.sh --skip-build" fi if matches_any "$path" "Package.swift" "Sources/TranscriptedCore/*" "Tests/TranscriptedCoreTests/*"; then - add_command "bash build.sh" + add_command "bash build.sh --no-open" add_command "bash run-tests.sh" add_command "bash run-integration-smoke.sh" add_command "swift test" @@ -129,12 +171,12 @@ if [ -n "$changed_paths" ]; then fi if matches_any "$path" "build-beta.sh" "scripts/entrypoints/build-beta.sh" "scripts/release/*" "docs/release-packaging.md" "docs/sparkle-updates.md" "Casks/*" "docs/appcast.xml"; then - add_command "bash build.sh" + add_command "bash build.sh --no-open" add_command "bash run-tests.sh" add_command "SKIP_NOTARIZATION=1 bash build-beta.sh " fi - if matches_any "$path" "README.md" "AGENT_START.md" "AGENTS.md" "CLAUDE.md" "CONTRIBUTING.md" "docs/*" ".agents/*" "scripts/dev/agent-preflight.sh"; then + if matches_any "$path" "README.md" "AGENT_START.md" "AGENTS.md" "CLAUDE.md" "CONTRIBUTING.md" "WORKFLOW.md" "docs/*" ".agents/*" ".github/*" "scripts/dev/agent-preflight.sh"; then add_command "scripts/dev/agent-preflight.sh" fi done <<< "$changed_paths" diff --git a/scripts/entrypoints/run-integration-smoke.sh b/scripts/entrypoints/run-integration-smoke.sh index 6f32d1c2..0d017ae8 100755 --- a/scripts/entrypoints/run-integration-smoke.sh +++ b/scripts/entrypoints/run-integration-smoke.sh @@ -11,7 +11,7 @@ # compiles only Foundation/AppKit sources in ~2s and must stay that fast for # the tight edit loop. This script pays the full Core + FluidAudio link cost. -set -e +set -euo pipefail ENTRYPOINT_DIR="$(cd "$(dirname "$0")" && pwd)" REPO_ROOT="$(cd "$ENTRYPOINT_DIR/../.." && pwd)" @@ -20,13 +20,55 @@ cd "$REPO_ROOT" SMOKE_BIN="$REPO_ROOT/build/app-core-integration-smoke" WAKE_SMOKE_BIN="$REPO_ROOT/build/wake-recovery-integration-smoke" DEPS_FRAMEWORK_ROOT="$REPO_ROOT/deps-frameworks" +DEPS_ARCHIVE="$REPO_ROOT/deps-libs/libDraftDeps.a" +DEPS_BUILD_STAMP="$REPO_ROOT/deps-libs/.build-deps-stamp" +DEPS_MODULE_ROOT="$REPO_ROOT/deps-modules" +TRANSCRIPTED_CORE_MODULE="$DEPS_MODULE_ROOT/TranscriptedCore.swiftmodule/arm64-apple-macos.swiftmodule" +ARGMAX_CORE_MODULE="$DEPS_MODULE_ROOT/ArgmaxCore.swiftmodule/arm64-apple-macos.swiftmodule" +WHISPERKIT_MODULE="$DEPS_MODULE_ROOT/WhisperKit.swiftmodule/arm64-apple-macos.swiftmodule" ESPEAK_FRAMEWORK="$DEPS_FRAMEWORK_ROOT/ESpeakNG.framework" -if [ ! -f "$REPO_ROOT/deps-libs/libDraftDeps.a" ] || [ ! -d "$REPO_ROOT/deps-modules" ] || [ ! -d "$ESPEAK_FRAMEWORK" ]; then +dependency_input_listing() { + { + printf '%s\n' "Package.swift" + printf '%s\n' "scripts/entrypoints/build-deps.sh" + find "Sources/TranscriptedCore" -type f ! -name "CLAUDE.md" + } | while IFS= read -r path; do + [ -e "$path" ] || continue + printf '%s\t%s\n' "$(stat -f '%m' "$path")" "$path" + done +} + +newest_dependency_input() { + dependency_input_listing | awk 'NR == 1 || $1 > max { max = $1; line = $0 } END { if (line != "") print line }' +} + +deps_build_stamp_info() { + if [ -f "$DEPS_BUILD_STAMP" ]; then + printf '%s\t%s\n' "$(stat -f '%m' "$DEPS_BUILD_STAMP")" "$DEPS_BUILD_STAMP" + fi +} + +if [ ! -f "$DEPS_ARCHIVE" ] || [ ! -f "$DEPS_BUILD_STAMP" ] || [ ! -d "$DEPS_MODULE_ROOT" ] || [ ! -f "$TRANSCRIPTED_CORE_MODULE" ] || [ ! -f "$ARGMAX_CORE_MODULE" ] || [ ! -f "$WHISPERKIT_MODULE" ] || [ ! -d "$ESPEAK_FRAMEWORK" ]; then echo "Dependencies not found — run build-deps.sh first." exit 1 fi +newest_input="$(newest_dependency_input)" +build_stamp="$(deps_build_stamp_info)" +IFS=$'\t' read -r newest_input_mtime newest_input_path <<< "$newest_input" +IFS=$'\t' read -r build_stamp_mtime build_stamp_path <<< "$build_stamp" +if [ -n "$newest_input_mtime" ] && [ -n "$build_stamp_mtime" ] && [ "$newest_input_mtime" -gt "$build_stamp_mtime" ]; then + echo "Dependencies are stale for TranscriptedCore." + echo "Newest input:" + echo " $newest_input_path" + echo "Built deps stamp:" + echo " $build_stamp_path" + echo "" + echo "Run: bash build-deps.sh --force" + exit 1 +fi + mkdir -p "$REPO_ROOT/build" # Build the -I flags for every module directory in deps-modules. diff --git a/scripts/entrypoints/run-tests.sh b/scripts/entrypoints/run-tests.sh index 5420ec2a..0a5de3a1 100755 --- a/scripts/entrypoints/run-tests.sh +++ b/scripts/entrypoints/run-tests.sh @@ -36,6 +36,11 @@ for arg in "$@"; do echo "Set FAST_TEST_COVERAGE=1 or pass --coverage to write LLVM coverage artifacts to $COVERAGE_DIR." exit 0 ;; + *) + echo "Unknown option: $arg" + echo "Usage: bash run-tests.sh [--coverage]" + exit 2 + ;; esac done