@mmerterden/multi-agent-pipeline 20.34.0 → 20.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +55 -1
- package/README.md +1 -0
- package/docs/facts.json +2 -2
- package/install/catalog-history.json +1 -1
- package/install/claude.mjs +16 -1
- package/install/templates/claude-hooks.json +23 -1
- package/manifest.json +324 -293
- package/package.json +1 -1
- package/pipeline/agents/bulk-reader.md +3 -0
- package/pipeline/commands/multi-agent/channels/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/office-on/SKILL.md +7 -3
- package/pipeline/commands/multi-agent/office-status/SKILL.md +3 -2
- package/pipeline/contract/CHANGELOG.md +9 -0
- package/pipeline/contract/README.md +1 -0
- package/pipeline/contract/fixtures/error-invalid-request.json +1 -1
- package/pipeline/contract/fixtures/error-unauthorized.json +1 -1
- package/pipeline/contract/fixtures/error-unsigned.json +1 -1
- package/pipeline/contract/manifest.json +14 -1
- package/pipeline/contract/types/index.d.ts +24 -2
- package/pipeline/lib/json-file-lock.mjs +61 -15
- package/pipeline/lib/office-remote.mjs +228 -0
- package/pipeline/lib/qr-code.mjs +360 -0
- package/pipeline/lib/repo-hygiene.sh +1 -0
- package/pipeline/lib/run-completion.mjs +17 -0
- package/pipeline/multi-agent-refs/channels/issue-comment.md +1 -1
- package/pipeline/multi-agent-refs/features/office-remote-protocol.md +166 -0
- package/pipeline/multi-agent-refs/features/office-threat-model.md +1 -1
- package/pipeline/multi-agent-refs/features/scope-check.md +16 -1
- package/pipeline/multi-agent-refs/features/verify-by-test.md +10 -0
- package/pipeline/multi-agent-refs/keychain.md +7 -0
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +6 -2
- package/pipeline/multi-agent-refs/phases/phase-1-plan.md +4 -6
- package/pipeline/multi-agent-refs/phases/phase-2-dev.md +9 -12
- package/pipeline/multi-agent-refs/phases/phase-3-review.md +9 -8
- package/pipeline/multi-agent-refs/phases/phase-4-commit.md +1 -4
- package/pipeline/multi-agent-refs/rules.md +3 -2
- package/pipeline/multi-agent-refs/tracker-contract.md +1 -1
- package/pipeline/office/NOTICE.md +4 -1
- package/pipeline/office/assets/char_12.png +0 -0
- package/pipeline/office/assets/char_13.png +0 -0
- package/pipeline/office/assets/char_14.png +0 -0
- package/pipeline/office/assets/char_15.png +0 -0
- package/pipeline/office/assets/char_16.png +0 -0
- package/pipeline/office/assets/char_17.png +0 -0
- package/pipeline/office/assets/char_18.png +0 -0
- package/pipeline/office/assets/char_19.png +0 -0
- package/pipeline/office/assets/char_20.png +0 -0
- package/pipeline/office/assets/char_21.png +0 -0
- package/pipeline/office/assets/char_22.png +0 -0
- package/pipeline/office/assets/char_23.png +0 -0
- package/pipeline/office/assets/char_24.png +0 -0
- package/pipeline/office/assets/char_25.png +0 -0
- package/pipeline/office/assets/char_26.png +0 -0
- package/pipeline/office/assets/char_27.png +0 -0
- package/pipeline/office/assets/char_28.png +0 -0
- package/pipeline/office/assets/char_29.png +0 -0
- package/pipeline/office/assets/char_30.png +0 -0
- package/pipeline/office/index.html +293 -49
- package/pipeline/schemas/agent-state.schema.json +18 -0
- package/pipeline/schemas/planning-output.schema.json +18 -0
- package/pipeline/schemas/pr-reach.schema.json +55 -0
- package/pipeline/schemas/prefs.schema.json +13 -7
- package/pipeline/schemas/triage-output.schema.json +12 -1
- package/pipeline/schemas/unattended-policy.json +2 -1
- package/pipeline/scripts/_guard_rules.py +449 -1
- package/pipeline/scripts/_test-gaming.mjs +36 -0
- package/pipeline/scripts/agent-guard.py +79 -19
- package/pipeline/scripts/anonymize-findings.mjs +99 -0
- package/pipeline/scripts/answer-check.mjs +113 -0
- package/pipeline/scripts/answer-check.sh +20 -0
- package/pipeline/scripts/autopilot-publish.mjs +9 -2
- package/pipeline/scripts/contract-server.mjs +35 -1
- package/pipeline/scripts/gate-ledger.mjs +144 -21
- package/pipeline/scripts/office-build.mjs +276 -5
- package/pipeline/scripts/office-control.mjs +70 -10
- package/pipeline/scripts/office-pair.mjs +407 -0
- package/pipeline/scripts/office-remote-client.mjs +463 -0
- package/pipeline/scripts/phase-tracker.sh +8 -0
- package/pipeline/scripts/phase0-exit-gate.mjs +40 -37
- package/pipeline/scripts/phone-devices.mjs +9 -1
- package/pipeline/scripts/pre-commit-check.sh +28 -2
- package/pipeline/scripts/pre-push-check.sh +6 -12
- package/pipeline/scripts/rework-base.mjs +87 -0
- package/pipeline/scripts/run-commit-check.mjs +104 -0
- package/pipeline/scripts/run-scope-guard.mjs +58 -3
- package/pipeline/scripts/run-scope-guard.sh +13 -1
- package/pipeline/scripts/scope-check-gate.mjs +70 -6
- package/pipeline/scripts/task-mirror.mjs +106 -15
- package/pipeline/scripts/task-mirror.sh +3 -2
- package/pipeline/scripts/test-stability-gate.mjs +112 -0
- package/pipeline/scripts/update-check.sh +7 -1
- package/pipeline/scripts/update-issue-progress.sh +73 -8
- package/pipeline/scripts/validate-planning.mjs +105 -1
- package/pipeline/scripts/validate-triage.mjs +42 -2
- package/pipeline/scripts/work-items.mjs +119 -1
- package/pipeline/scripts/worktree-prepare-all.mjs +200 -0
- package/pipeline/scripts/worktree-prepare.sh +40 -4
- package/pipeline/scripts/write-state.mjs +67 -24
- package/pipeline/skills/.skill-manifest.json +5 -5
- package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +5 -5
- package/pipeline/skills/shared/core/google-play-compliance/references/rules.yml +1 -1
- package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-office-status/SKILL.md +2 -2
- package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +14 -0
- package/pipeline/skills/shared/external/android-architecture/references/patterns.md +6 -0
- package/pipeline/skills/shared/external/android-build-quality-gates/references/patterns.md +10 -0
- package/pipeline/skills/shared/external/android-datastore/references/patterns.md +11 -0
- package/pipeline/skills/shared/external/android-design-tokens-codegen/references/patterns.md +8 -0
- package/pipeline/skills/shared/external/android-jetpack-compose-expert/references/patterns.md +6 -0
- package/pipeline/skills/shared/external/android-mvi-viewmodel/references/patterns.md +10 -0
- package/pipeline/skills/shared/external/android-performance/references/patterns.md +13 -0
- package/pipeline/skills/shared/external/android-security/references/patterns.md +16 -0
- package/pipeline/skills/shared/external/api-patterns/references/contract-details.md +9 -0
- package/pipeline/skills/shared/external/api-security-best-practices/references/abuse-controls.md +12 -0
- package/pipeline/skills/shared/external/api-security-best-practices/references/identity-and-access.md +13 -0
- package/pipeline/skills/shared/external/api-security-best-practices/references/request-handling.md +12 -0
- package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +9 -0
- package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +8 -0
- package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +10 -0
- package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +21 -0
- package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +9 -0
- package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +7 -0
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +2 -2
- package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +10 -0
- package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +10 -1
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +22 -0
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +17 -0
- package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +22 -0
- package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +17 -0
- package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +9 -0
- package/pipeline/skills/shared/external/authentication/references/passkeys.md +11 -0
- package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +13 -0
- package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +9 -0
- package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +10 -0
- package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +15 -0
- package/pipeline/skills/shared/external/compose-components/references/patterns.md +7 -0
- package/pipeline/skills/shared/external/compose-navigation/references/patterns.md +7 -0
- package/pipeline/skills/shared/external/compose-testing/references/patterns.md +15 -0
- package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +9 -0
- package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +11 -0
- package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +10 -0
- package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +13 -0
- package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +20 -0
- package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +16 -0
- package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +16 -0
- package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +14 -0
- package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +13 -0
- package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +10 -0
- package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +10 -0
- package/pipeline/skills/shared/external/fastapi-pro/references/app-structure.md +10 -0
- package/pipeline/skills/shared/external/gradle-kotlin-dsl/references/patterns.md +17 -0
- package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +12 -0
- package/pipeline/skills/shared/external/hig-app-icons/references/icon-workflow.md +10 -0
- package/pipeline/skills/shared/external/hig-components-content/references/content-views.md +10 -0
- package/pipeline/skills/shared/external/hig-components-layout/references/bars-and-tabs.md +11 -0
- package/pipeline/skills/shared/external/hig-foundations/references/foundations-detail.md +13 -0
- package/pipeline/skills/shared/external/hig-foundations/references/type-color-spacing.md +13 -0
- package/pipeline/skills/shared/external/hig-patterns/references/onboarding-and-states.md +11 -0
- package/pipeline/skills/shared/external/hig-platforms/references/ipad-layout.md +12 -0
- package/pipeline/skills/shared/external/hig-technologies/references/technologies.md +16 -0
- package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +9 -0
- package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +18 -0
- package/pipeline/skills/shared/external/ios-accessibility/references/large-content-and-appearance.md +9 -0
- package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +9 -0
- package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +7 -0
- package/pipeline/skills/shared/external/ios-coding-standard/references/STANDARD.md +13 -0
- package/pipeline/skills/shared/external/ios-design-review/references/appearance-matrix.md +9 -0
- package/pipeline/skills/shared/external/ios-design-review/references/checklist.md +15 -0
- package/pipeline/skills/shared/external/ios-haptics/references/core-haptics.md +11 -0
- package/pipeline/skills/shared/external/ios-iphone-duo/references/api-map.md +11 -0
- package/pipeline/skills/shared/external/ios-iphone-duo/references/fixed-assumptions.md +15 -0
- package/pipeline/skills/shared/external/ios-iphone-duo/references/poses-and-panes.md +11 -0
- package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +18 -0
- package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +19 -0
- package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +15 -0
- package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +7 -0
- package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +8 -0
- package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +12 -0
- package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +17 -0
- package/pipeline/skills/shared/external/ios-security/references/app-review-guidelines.md +8 -0
- package/pipeline/skills/shared/external/ios-security/references/cryptokit-advanced.md +14 -0
- package/pipeline/skills/shared/external/ios-security/references/file-storage-patterns.md +8 -0
- package/pipeline/skills/shared/external/ios-security/references/privacy-manifest.md +9 -0
- package/pipeline/skills/shared/external/ios-sf-symbols/references/effects-and-custom-symbols.md +9 -0
- package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +13 -0
- package/pipeline/skills/shared/external/kotlin-coroutines-expert/references/patterns.md +8 -0
- package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +21 -0
- package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +9 -0
- package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +12 -0
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +16 -0
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +21 -0
- package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +10 -0
- package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +9 -0
- package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +12 -0
- package/pipeline/skills/shared/external/nodejs-backend-patterns/references/runtime-patterns.md +11 -0
- package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +11 -0
- package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +18 -0
- package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +11 -0
- package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +11 -0
- package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +15 -0
- package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +14 -0
- package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +14 -0
- package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +14 -0
- package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +14 -0
- package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +10 -0
- package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +12 -0
- package/pipeline/skills/shared/external/retrofit-networking/references/patterns.md +14 -0
- package/pipeline/skills/shared/external/room-database/references/patterns.md +16 -0
- package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +10 -0
- package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +10 -0
- package/pipeline/skills/shared/external/spm-build-analysis/references/build-optimization-sources.md +14 -0
- package/pipeline/skills/shared/external/spm-build-analysis/references/spm-analysis-checks.md +19 -0
- package/pipeline/skills/shared/external/storekit/references/core-patterns.md +13 -0
- package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +31 -0
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +8 -0
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +9 -0
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +10 -0
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +8 -0
- package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +24 -0
- package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +8 -0
- package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +14 -0
- package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +12 -0
- package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +10 -0
- package/pipeline/skills/shared/external/swift-concurrency-expert/references/swift-6-2-concurrency.md +8 -0
- package/pipeline/skills/shared/external/swift-concurrency-pro/references/actors.md +10 -0
- package/pipeline/skills/shared/external/swift-concurrency-pro/references/cancellation.md +7 -0
- package/pipeline/skills/shared/external/swift-concurrency-pro/references/interop.md +10 -0
- package/pipeline/skills/shared/external/swift-concurrency-pro/references/new-features.md +11 -0
- package/pipeline/skills/shared/external/swift-concurrency-pro/references/structured.md +9 -0
- package/pipeline/skills/shared/external/swift-concurrency-pro/references/testing.md +13 -0
- package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +7 -0
- package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +14 -0
- package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +16 -0
- package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +17 -0
- package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +19 -0
- package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +16 -0
- package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +19 -0
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +18 -0
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +16 -0
- package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +17 -0
- package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +17 -0
- package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +17 -0
- package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +18 -0
- package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +21 -0
- package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +16 -0
- package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +16 -0
- package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +9 -0
- package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +24 -0
- package/pipeline/skills/shared/external/swift-testing-pro/references/async-tests.md +9 -0
- package/pipeline/skills/shared/external/swift-testing-pro/references/new-features.md +11 -0
- package/pipeline/skills/shared/external/swift-testing-pro/references/writing-better-tests.md +13 -0
- package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +8 -0
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +17 -0
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +12 -0
- package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +11 -0
- package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +7 -0
- package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +13 -0
- package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +24 -0
- package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +12 -0
- package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +15 -0
- package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +15 -0
- package/pipeline/skills/shared/external/swiftui-animation/references/scroll-effects.md +12 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/accessibility-patterns.md +11 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-advanced.md +7 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-basics.md +13 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-transitions.md +9 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/charts-accessibility.md +9 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/charts.md +17 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/image-optimization.md +9 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/latest-apis.md +10 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/layout-best-practices.md +12 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/list-patterns.md +12 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/macos-scenes.md +13 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/macos-views.md +12 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/macos-window-styling.md +12 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/performance-patterns.md +7 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/scroll-patterns.md +9 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/sheet-navigation-patterns.md +10 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/state-management.md +18 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/view-structure.md +16 -0
- package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +11 -0
- package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +8 -0
- package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +14 -0
- package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +9 -0
- package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +22 -0
- package/pipeline/skills/shared/external/swiftui-liquid-glass/references/uikit-glass.md +14 -0
- package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +10 -0
- package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +10 -0
- package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +13 -0
- package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +10 -0
- package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +14 -0
- package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +30 -0
- package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +16 -0
- package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +11 -0
- package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +11 -0
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +10 -0
- package/pipeline/skills/shared/external/swiftui-performance-audit/references/code-smells.md +8 -0
- package/pipeline/skills/shared/external/swiftui-ui-patterns/references/app-wiring.md +12 -0
- package/pipeline/skills/shared/external/swiftui-ui-patterns/references/menu-bar.md +10 -0
- package/pipeline/skills/shared/external/swiftui-ui-patterns/references/navigationstack.md +11 -0
- package/pipeline/skills/shared/external/swiftui-ui-patterns/references/scroll-reveal.md +12 -0
- package/pipeline/skills/shared/external/swiftui-ui-patterns/references/sheets.md +14 -0
- package/pipeline/skills/shared/external/swiftui-ui-patterns/references/tabview.md +10 -0
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +10 -0
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +13 -0
- package/pipeline/skills/shared/external/swiftui-view-refactor/references/mv-patterns.md +12 -0
- package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +8 -0
- package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +8 -0
- package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +14 -0
- package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +17 -0
- package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +11 -0
- package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +11 -0
- package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +27 -0
- package/pipeline/skills/shared/external/xcode-build-fixer/references/build-settings-best-practices.md +9 -0
- package/pipeline/skills/shared/external/xcode-build-fixer/references/fix-patterns.md +7 -0
- package/pipeline/skills/shared/external/xcode-build-orchestrator/references/build-settings-best-practices.md +9 -0
- package/pipeline/skills/shared/external/xcode-compilation-analyzer/references/build-optimization-sources.md +14 -0
- package/pipeline/skills/shared/external/xcode-compilation-analyzer/references/code-compilation-checks.md +8 -0
- package/pipeline/skills/shared/external/xcode-project-analyzer/references/build-optimization-sources.md +14 -0
- package/pipeline/skills/shared/external/xcode-project-analyzer/references/build-settings-best-practices.md +9 -0
- package/pipeline/skills/shared/external/xcode-project-analyzer/references/project-audit-checks.md +13 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* qr-code.mjs - a QR Code Model 2 encoder (ISO/IEC 18004), byte mode only.
|
|
3
|
+
*
|
|
4
|
+
* Used by office-pair to show the pairing link on the machine's own screen.
|
|
5
|
+
* Byte mode covers any UTF-8 text; the smallest version that fits the text at
|
|
6
|
+
* the requested error-correction level is chosen, and the mask with the lowest
|
|
7
|
+
* penalty score is applied.
|
|
8
|
+
*
|
|
9
|
+
* Zero runtime dependencies (ADR-0004).
|
|
10
|
+
*
|
|
11
|
+
* @module pipeline/lib/qr-code
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const ECC_FORMAT_BITS = { L: 1, M: 0, Q: 3, H: 2 };
|
|
15
|
+
|
|
16
|
+
// Per version (index 1..40): error-correction codewords per block, and the
|
|
17
|
+
// number of blocks, for each level (ISO/IEC 18004 table 9).
|
|
18
|
+
const ECC_PER_BLOCK = {
|
|
19
|
+
L: [
|
|
20
|
+
-1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30,
|
|
21
|
+
30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30,
|
|
22
|
+
],
|
|
23
|
+
M: [
|
|
24
|
+
-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28,
|
|
25
|
+
28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
|
|
26
|
+
],
|
|
27
|
+
Q: [
|
|
28
|
+
-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30,
|
|
29
|
+
30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30,
|
|
30
|
+
],
|
|
31
|
+
H: [
|
|
32
|
+
-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30,
|
|
33
|
+
30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30,
|
|
34
|
+
],
|
|
35
|
+
};
|
|
36
|
+
const BLOCKS = {
|
|
37
|
+
L: [
|
|
38
|
+
-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14,
|
|
39
|
+
15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25,
|
|
40
|
+
],
|
|
41
|
+
M: [
|
|
42
|
+
-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23,
|
|
43
|
+
25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49,
|
|
44
|
+
],
|
|
45
|
+
Q: [
|
|
46
|
+
-1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34,
|
|
47
|
+
34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68,
|
|
48
|
+
],
|
|
49
|
+
H: [
|
|
50
|
+
-1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35,
|
|
51
|
+
37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81,
|
|
52
|
+
],
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/** Modules available for data and error correction in a version. */
|
|
56
|
+
export function rawDataModules(version) {
|
|
57
|
+
let n = (16 * version + 128) * version + 64;
|
|
58
|
+
if (version >= 2) {
|
|
59
|
+
const align = Math.floor(version / 7) + 2;
|
|
60
|
+
n -= (25 * align - 10) * align - 55;
|
|
61
|
+
if (version >= 7) n -= 36;
|
|
62
|
+
}
|
|
63
|
+
return n;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Data codewords of a version at a level. */
|
|
67
|
+
export function dataCodewords(version, ecc) {
|
|
68
|
+
return (
|
|
69
|
+
Math.floor(rawDataModules(version) / 8) - ECC_PER_BLOCK[ecc][version] * BLOCKS[ecc][version]
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// GF(256) with the QR polynomial x^8 + x^4 + x^3 + x^2 + 1.
|
|
74
|
+
function gfMul(x, y) {
|
|
75
|
+
let z = 0;
|
|
76
|
+
for (let i = 7; i >= 0; i--) {
|
|
77
|
+
z = (z << 1) ^ ((z >>> 7) * 0x11d);
|
|
78
|
+
z ^= ((y >>> i) & 1) * x;
|
|
79
|
+
}
|
|
80
|
+
return z & 0xff;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function rsDivisor(degree) {
|
|
84
|
+
const result = new Array(degree).fill(0);
|
|
85
|
+
result[degree - 1] = 1;
|
|
86
|
+
let root = 1;
|
|
87
|
+
for (let i = 0; i < degree; i++) {
|
|
88
|
+
for (let j = 0; j < degree; j++) {
|
|
89
|
+
result[j] = gfMul(result[j], root);
|
|
90
|
+
if (j + 1 < degree) result[j] ^= result[j + 1];
|
|
91
|
+
}
|
|
92
|
+
root = gfMul(root, 0x02);
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Reed-Solomon error-correction codewords for one block. */
|
|
98
|
+
export function reedSolomon(data, degree) {
|
|
99
|
+
const divisor = rsDivisor(degree);
|
|
100
|
+
const result = new Array(degree).fill(0);
|
|
101
|
+
for (const b of data) {
|
|
102
|
+
const factor = b ^ result.shift();
|
|
103
|
+
result.push(0);
|
|
104
|
+
divisor.forEach((coef, i) => {
|
|
105
|
+
result[i] ^= gfMul(coef, factor);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function alignmentPositions(version) {
|
|
112
|
+
if (version === 1) return [];
|
|
113
|
+
const count = Math.floor(version / 7) + 2;
|
|
114
|
+
const size = version * 4 + 17;
|
|
115
|
+
const step = version === 32 ? 26 : Math.ceil((version * 4 + 4) / (count * 2 - 2)) * 2;
|
|
116
|
+
const out = [6];
|
|
117
|
+
for (let pos = size - 7; out.length < count; pos -= step) out.splice(1, 0, pos);
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function bch(value, bits, poly, polyDegree) {
|
|
122
|
+
let rem = value << polyDegree;
|
|
123
|
+
for (let i = bits + polyDegree - 1; i >= polyDegree; i--) {
|
|
124
|
+
if ((rem >>> i) & 1) rem ^= poly << (i - polyDegree);
|
|
125
|
+
}
|
|
126
|
+
return (value << polyDegree) | rem;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** The 15 format bits for a level and mask, already XORed with the fixed mask. */
|
|
130
|
+
export function formatBits(ecc, mask) {
|
|
131
|
+
return bch((ECC_FORMAT_BITS[ecc] << 3) | mask, 5, 0x537, 10) ^ 0x5412;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function codewords(bytes, version, ecc) {
|
|
135
|
+
const capacityBits = dataCodewords(version, ecc) * 8;
|
|
136
|
+
const bits = [];
|
|
137
|
+
const put = (value, length) => {
|
|
138
|
+
for (let i = length - 1; i >= 0; i--) bits.push((value >>> i) & 1);
|
|
139
|
+
};
|
|
140
|
+
put(0b0100, 4);
|
|
141
|
+
put(bytes.length, version < 10 ? 8 : 16);
|
|
142
|
+
for (const b of bytes) put(b, 8);
|
|
143
|
+
put(0, Math.min(4, capacityBits - bits.length));
|
|
144
|
+
put(0, (8 - (bits.length % 8)) % 8);
|
|
145
|
+
for (let pad = 0xec; bits.length < capacityBits; pad ^= 0xec ^ 0x11) put(pad, 8);
|
|
146
|
+
const data = [];
|
|
147
|
+
for (let i = 0; i < bits.length; i += 8) {
|
|
148
|
+
data.push(bits.slice(i, i + 8).reduce((a, b) => (a << 1) | b, 0));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const blocks = BLOCKS[ecc][version];
|
|
152
|
+
const eccLen = ECC_PER_BLOCK[ecc][version];
|
|
153
|
+
const raw = Math.floor(rawDataModules(version) / 8);
|
|
154
|
+
const shortBlocks = blocks - (raw % blocks);
|
|
155
|
+
const shortLen = Math.floor(raw / blocks);
|
|
156
|
+
const groups = [];
|
|
157
|
+
for (let i = 0, k = 0; i < blocks; i++) {
|
|
158
|
+
const len = shortLen - eccLen + (i < shortBlocks ? 0 : 1);
|
|
159
|
+
const chunk = data.slice(k, k + len);
|
|
160
|
+
k += len;
|
|
161
|
+
groups.push({ data: chunk, ecc: reedSolomon(chunk, eccLen) });
|
|
162
|
+
}
|
|
163
|
+
const out = [];
|
|
164
|
+
for (let i = 0; i <= shortLen - eccLen; i++) {
|
|
165
|
+
for (const g of groups) if (i < g.data.length) out.push(g.data[i]);
|
|
166
|
+
}
|
|
167
|
+
for (let i = 0; i < eccLen; i++) for (const g of groups) out.push(g.ecc[i]);
|
|
168
|
+
return out;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const MASKS = [
|
|
172
|
+
(x, y) => (x + y) % 2 === 0,
|
|
173
|
+
(x, y) => y % 2 === 0,
|
|
174
|
+
(x) => x % 3 === 0,
|
|
175
|
+
(x, y) => (x + y) % 3 === 0,
|
|
176
|
+
(x, y) => (Math.floor(x / 3) + Math.floor(y / 2)) % 2 === 0,
|
|
177
|
+
(x, y) => ((x * y) % 2) + ((x * y) % 3) === 0,
|
|
178
|
+
(x, y) => (((x * y) % 2) + ((x * y) % 3)) % 2 === 0,
|
|
179
|
+
(x, y) => (((x + y) % 2) + ((x * y) % 3)) % 2 === 0,
|
|
180
|
+
];
|
|
181
|
+
|
|
182
|
+
function baseMatrix(version) {
|
|
183
|
+
const size = version * 4 + 17;
|
|
184
|
+
const dark = Array.from({ length: size }, () => new Array(size).fill(false));
|
|
185
|
+
const fixed = Array.from({ length: size }, () => new Array(size).fill(false));
|
|
186
|
+
const set = (x, y, v) => {
|
|
187
|
+
dark[y][x] = v;
|
|
188
|
+
fixed[y][x] = true;
|
|
189
|
+
};
|
|
190
|
+
for (let i = 0; i < size; i++) {
|
|
191
|
+
set(6, i, i % 2 === 0);
|
|
192
|
+
set(i, 6, i % 2 === 0);
|
|
193
|
+
}
|
|
194
|
+
const finder = (cx, cy) => {
|
|
195
|
+
for (let dy = -4; dy <= 4; dy++) {
|
|
196
|
+
for (let dx = -4; dx <= 4; dx++) {
|
|
197
|
+
const x = cx + dx;
|
|
198
|
+
const y = cy + dy;
|
|
199
|
+
if (x < 0 || y < 0 || x >= size || y >= size) continue;
|
|
200
|
+
const d = Math.max(Math.abs(dx), Math.abs(dy));
|
|
201
|
+
set(x, y, d !== 2 && d !== 4);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
finder(3, 3);
|
|
206
|
+
finder(size - 4, 3);
|
|
207
|
+
finder(3, size - 4);
|
|
208
|
+
const align = alignmentPositions(version);
|
|
209
|
+
for (const ay of align) {
|
|
210
|
+
for (const ax of align) {
|
|
211
|
+
const corner =
|
|
212
|
+
(ax === 6 && ay === 6) || (ax === 6 && ay === size - 7) || (ax === size - 7 && ay === 6);
|
|
213
|
+
if (corner) continue;
|
|
214
|
+
for (let dy = -2; dy <= 2; dy++) {
|
|
215
|
+
for (let dx = -2; dx <= 2; dx++) {
|
|
216
|
+
set(ax + dx, ay + dy, Math.max(Math.abs(dx), Math.abs(dy)) !== 1);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
for (let i = 0; i < 9; i++) {
|
|
222
|
+
fixed[8][i] = fixed[i][8] = true;
|
|
223
|
+
if (i < 8) fixed[8][size - 1 - i] = fixed[size - 1 - i][8] = true;
|
|
224
|
+
}
|
|
225
|
+
set(8, size - 8, true);
|
|
226
|
+
if (version >= 7) {
|
|
227
|
+
const v = bch(version, 6, 0x1f25, 12);
|
|
228
|
+
for (let i = 0; i < 18; i++) {
|
|
229
|
+
const bit = ((v >>> i) & 1) === 1;
|
|
230
|
+
const a = size - 11 + (i % 3);
|
|
231
|
+
const b = Math.floor(i / 3);
|
|
232
|
+
set(a, b, bit);
|
|
233
|
+
set(b, a, bit);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return { size, dark, fixed };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function drawFormat(dark, size, ecc, mask) {
|
|
240
|
+
const f = formatBits(ecc, mask);
|
|
241
|
+
const bit = (i) => ((f >>> i) & 1) === 1;
|
|
242
|
+
for (let i = 0; i <= 5; i++) dark[i][8] = bit(i);
|
|
243
|
+
dark[7][8] = bit(6);
|
|
244
|
+
dark[8][8] = bit(7);
|
|
245
|
+
dark[8][7] = bit(8);
|
|
246
|
+
for (let i = 9; i < 15; i++) dark[8][14 - i] = bit(i);
|
|
247
|
+
for (let i = 0; i < 8; i++) dark[8][size - 1 - i] = bit(i);
|
|
248
|
+
for (let i = 8; i < 15; i++) dark[size - 15 + i][8] = bit(i);
|
|
249
|
+
dark[size - 8][8] = true;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function placeData(dark, fixed, size, words) {
|
|
253
|
+
let i = 0;
|
|
254
|
+
const total = words.length * 8;
|
|
255
|
+
for (let right = size - 1; right >= 1; right -= 2) {
|
|
256
|
+
if (right === 6) right = 5;
|
|
257
|
+
for (let vert = 0; vert < size; vert++) {
|
|
258
|
+
for (let j = 0; j < 2; j++) {
|
|
259
|
+
const x = right - j;
|
|
260
|
+
const upward = ((right + 1) & 2) === 0;
|
|
261
|
+
const y = upward ? size - 1 - vert : vert;
|
|
262
|
+
if (fixed[y][x] || i >= total) continue;
|
|
263
|
+
dark[y][x] = ((words[i >>> 3] >>> (7 - (i & 7))) & 1) === 1;
|
|
264
|
+
i++;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** The ISO penalty score of a finished matrix: runs, 2x2 blocks, finder-like patterns, balance. */
|
|
271
|
+
export function penalty(dark) {
|
|
272
|
+
const size = dark.length;
|
|
273
|
+
let score = 0;
|
|
274
|
+
const lineScore = (get) => {
|
|
275
|
+
let s = 0;
|
|
276
|
+
for (let a = 0; a < size; a++) {
|
|
277
|
+
let run = 1;
|
|
278
|
+
for (let b = 1; b <= size; b++) {
|
|
279
|
+
if (b < size && get(a, b) === get(a, b - 1)) {
|
|
280
|
+
run++;
|
|
281
|
+
} else {
|
|
282
|
+
if (run >= 5) s += run - 2;
|
|
283
|
+
run = 1;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
for (let b = 0; b + 10 < size; b++) {
|
|
287
|
+
const seq = Array.from({ length: 11 }, (_, k) => get(a, b + k));
|
|
288
|
+
const p1 = [1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0];
|
|
289
|
+
const p2 = [0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1];
|
|
290
|
+
if (seq.every((v, k) => v === Boolean(p1[k])) || seq.every((v, k) => v === Boolean(p2[k])))
|
|
291
|
+
s += 40;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return s;
|
|
295
|
+
};
|
|
296
|
+
score += lineScore((a, b) => dark[a][b]);
|
|
297
|
+
score += lineScore((a, b) => dark[b][a]);
|
|
298
|
+
let darkCount = 0;
|
|
299
|
+
for (let y = 0; y < size; y++) {
|
|
300
|
+
for (let x = 0; x < size; x++) {
|
|
301
|
+
if (dark[y][x]) darkCount++;
|
|
302
|
+
if (
|
|
303
|
+
y + 1 < size &&
|
|
304
|
+
x + 1 < size &&
|
|
305
|
+
dark[y][x] === dark[y][x + 1] &&
|
|
306
|
+
dark[y][x] === dark[y + 1][x] &&
|
|
307
|
+
dark[y][x] === dark[y + 1][x + 1]
|
|
308
|
+
)
|
|
309
|
+
score += 3;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
const total = size * size;
|
|
313
|
+
score += (Math.ceil(Math.abs(darkCount * 20 - total * 10) / total) - 1) * 10;
|
|
314
|
+
return score;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Encode text as a QR code.
|
|
319
|
+
*
|
|
320
|
+
* @param {string} text
|
|
321
|
+
* @param {{ecc?: "L"|"M"|"Q"|"H", mask?: number}} [opts] mask forces one of 0..7
|
|
322
|
+
* @returns {{version: number, size: number, ecc: string, mask: number, modules: boolean[][]}}
|
|
323
|
+
*/
|
|
324
|
+
export function encodeQr(text, { ecc = "M", mask } = {}) {
|
|
325
|
+
if (!ECC_PER_BLOCK[ecc]) throw new Error(`unknown error-correction level ${ecc}`);
|
|
326
|
+
const bytes = [...Buffer.from(String(text), "utf8")];
|
|
327
|
+
let version = 1;
|
|
328
|
+
const fits = (v) => 4 + (v < 10 ? 8 : 16) + bytes.length * 8 <= dataCodewords(v, ecc) * 8;
|
|
329
|
+
while (version <= 40 && !fits(version)) version++;
|
|
330
|
+
if (version > 40) throw new Error("text is too long for a QR code");
|
|
331
|
+
const words = codewords(bytes, version, ecc);
|
|
332
|
+
const { size, dark, fixed } = baseMatrix(version);
|
|
333
|
+
placeData(dark, fixed, size, words);
|
|
334
|
+
const candidates = Number.isInteger(mask) ? [mask] : [0, 1, 2, 3, 4, 5, 6, 7];
|
|
335
|
+
let best = null;
|
|
336
|
+
for (const m of candidates) {
|
|
337
|
+
const grid = dark.map((row, y) => row.map((v, x) => (fixed[y][x] ? v : v !== MASKS[m](x, y))));
|
|
338
|
+
drawFormat(grid, size, ecc, m);
|
|
339
|
+
const score = penalty(grid);
|
|
340
|
+
if (!best || score < best.score) best = { score, grid, mask: m };
|
|
341
|
+
}
|
|
342
|
+
return { version, size, ecc, mask: best.mask, modules: best.grid };
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/** The code as one SVG path (one unit per module, a four-module quiet zone). */
|
|
346
|
+
export function qrSvg(qr, { scale = 6 } = {}) {
|
|
347
|
+
const q = 4;
|
|
348
|
+
const n = qr.size + q * 2;
|
|
349
|
+
let d = "";
|
|
350
|
+
qr.modules.forEach((row, y) =>
|
|
351
|
+
row.forEach((on, x) => {
|
|
352
|
+
if (on) d += `M${x + q} ${y + q}h1v1h-1z`;
|
|
353
|
+
}),
|
|
354
|
+
);
|
|
355
|
+
return (
|
|
356
|
+
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${n} ${n}" width="${n * scale}" ` +
|
|
357
|
+
`height="${n * scale}" shape-rendering="crispEdges"><rect width="${n}" height="${n}" ` +
|
|
358
|
+
`fill="#fff"/><path d="${d}" fill="#000"/></svg>`
|
|
359
|
+
);
|
|
360
|
+
}
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
* `commit.sha`, a `pr`, a runner `publish` record, a `projects[].commit`
|
|
12
12
|
* or `projects[].pr`, or an explicit no-commit decision
|
|
13
13
|
* (`phases["4"].status: "skipped"` with a `notes` reason).
|
|
14
|
+
* A repo commit counts as delivered once its push outcome is recorded
|
|
15
|
+
* (`projects[].pushStatus`) or it has a pull request.
|
|
14
16
|
* 3. The plan's coverage was judged, unless the mode declares
|
|
15
17
|
* `commits: false`: a `plan-coverage` entry in `gates[]` with a passing
|
|
16
18
|
* verdict. plan-coverage-gate.mjs records it attended and unattended.
|
|
@@ -101,6 +103,16 @@ export function phase4Evidence(state) {
|
|
|
101
103
|
return Boolean(p4 && p4.status === "skipped" && typeof p4.notes === "string" && p4.notes.trim());
|
|
102
104
|
}
|
|
103
105
|
|
|
106
|
+
const PUSH_RECORDED = new Set(["pushed", "rebase-pushed", "skipped"]);
|
|
107
|
+
|
|
108
|
+
/** Repos whose commit has neither a recorded push outcome nor a pull request. */
|
|
109
|
+
export function unpushedRepos(state) {
|
|
110
|
+
const projects = Array.isArray(state.projects) ? state.projects : [];
|
|
111
|
+
return projects
|
|
112
|
+
.filter((p) => p && hasSha(p.commit) && !hasPr(p.pr) && !PUSH_RECORDED.has(p.pushStatus))
|
|
113
|
+
.map((p, i) => p.project || p.name || `#${i + 1}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
104
116
|
/** True when a passing plan-coverage verdict is in the gate ledger. */
|
|
105
117
|
export function planCoverageRecorded(state) {
|
|
106
118
|
const gates = Array.isArray(state.gates) ? state.gates : [];
|
|
@@ -138,6 +150,11 @@ export function completionGaps(state) {
|
|
|
138
150
|
'no Phase 4 evidence: record commit.sha (or pr), or the no-commit decision as phases["4"] = {status: "skipped", notes: "<why>"}',
|
|
139
151
|
);
|
|
140
152
|
}
|
|
153
|
+
for (const name of unpushedRepos(state)) {
|
|
154
|
+
gaps.push(
|
|
155
|
+
`repo ${name} has a commit with no recorded push: set projects[].pushStatus to "pushed" or "rebase-pushed", or "skipped" when the push is left to the user (Phase 4 push policy)`,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
141
158
|
if (!planCoverageRecorded(state)) {
|
|
142
159
|
gaps.push(
|
|
143
160
|
"no passing plan-coverage verdict in gates[]: run plan-coverage-gate.mjs --state <agent-state.json> (Phase 4 Step 0a)",
|
|
@@ -132,7 +132,7 @@ gh issue comment "$ISSUE_NUMBER" --repo "$ORG/$REPO" --body-file /tmp/channels-$
|
|
|
132
132
|
bash $HOME/.claude/scripts/update-issue-progress.sh "$TASK_ID"
|
|
133
133
|
```
|
|
134
134
|
|
|
135
|
-
If either call fails, the channel adapter MUST NOT mark the issue channel as completed in `agent-state.json`. The user sees a clear error and can retry.
|
|
135
|
+
If either call fails, the channel adapter MUST NOT mark the issue channel as completed in `agent-state.json`. The user sees a clear error and can retry. The order is enforced: `update-issue-progress.sh` exits 5 and leaves the body unchanged unless the authenticated account has a comment on the issue since `state.startedAt` that carries the `Ref: #N` footer.
|
|
136
136
|
|
|
137
137
|
## Drift detection
|
|
138
138
|
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Office remote protocol
|
|
2
|
+
|
|
3
|
+
<!-- toc -->
|
|
4
|
+
- [Parts and trust](#parts-and-trust)
|
|
5
|
+
- [Account](#account)
|
|
6
|
+
- [Pairing](#pairing)
|
|
7
|
+
- [Transport](#transport)
|
|
8
|
+
- [Passkey tiers](#passkey-tiers)
|
|
9
|
+
- [Audit](#audit)
|
|
10
|
+
- [Decisions](#decisions)
|
|
11
|
+
- [To verify on a real phone](#to-verify-on-a-real-phone)
|
|
12
|
+
<!-- /toc -->
|
|
13
|
+
|
|
14
|
+
How a phone reaches the phone routes of the local contract server through a
|
|
15
|
+
relay on the user's site, without the machine opening a port and without the
|
|
16
|
+
relay or the site being able to read a payload or act on their own.
|
|
17
|
+
|
|
18
|
+
The request layer exists and is not changed here: every phone request is signed
|
|
19
|
+
with the device's Ed25519 key (`MA-PHONE-SIG-1`), and `_phone-auth.mjs` checks
|
|
20
|
+
the device, the window, the nonce, the scopes and writes the audit line
|
|
21
|
+
(`phone-api.md`). This document adds what leaving the machine requires: an
|
|
22
|
+
end-to-end layer around those requests, the relay that carries it, pairing that
|
|
23
|
+
enrols the device through the existing registry, and a passkey on top of the
|
|
24
|
+
device signature for the routes that change something.
|
|
25
|
+
|
|
26
|
+
## Parts and trust
|
|
27
|
+
|
|
28
|
+
| Part | Runs | Holds | Trusted for |
|
|
29
|
+
|---|---|---|---|
|
|
30
|
+
| Phone page | the phone browser, on the site origin (behind the owner session) | the device signing key and the session key pair (non-extractable WebCrypto), a passkey | showing data, asking the user, signing |
|
|
31
|
+
| Site + relay | the user's site (serverless functions + database) | the owner's account, pairing records, ciphertext queues | delivering ciphertext, nothing else |
|
|
32
|
+
| Machine client | the user's machine, started with the office | the machine key pair, the audit of remote frames | decrypting, checking the passkey tier, forwarding to the local server |
|
|
33
|
+
| Local server | the user's machine, 127.0.0.1 only | the device registry, nonces, audit | verifying the device signature and executing phone routes, as today |
|
|
34
|
+
|
|
35
|
+
The relay and its database are treated as compromised: they can drop, delay,
|
|
36
|
+
replay and reorder frames. None of that lets them read a frame, forge a device
|
|
37
|
+
signature or pass the passkey check. The phone page itself is the trust anchor
|
|
38
|
+
on the phone side, as for any web app holding keys: a site that serves a
|
|
39
|
+
modified page can act as the phone. The page therefore loads no third-party
|
|
40
|
+
script, runs under a strict CSP and keeps its keys non-extractable, and the
|
|
41
|
+
machine still applies every check of its own (tiers, routes, counters).
|
|
42
|
+
|
|
43
|
+
## Account
|
|
44
|
+
|
|
45
|
+
The first release serves the owner's account only and reuses the site's
|
|
46
|
+
existing owner session rather than adding a second login: the phone page and
|
|
47
|
+
every phone relay route require it, and state-changing routes also require a
|
|
48
|
+
same-origin request. The session only lets the phone use the relay for its
|
|
49
|
+
own pairings; it never authorizes an action on the machine, which still
|
|
50
|
+
checks the device signature, the passkey tier and the route itself. Invited
|
|
51
|
+
accounts are a later step.
|
|
52
|
+
|
|
53
|
+
## Pairing
|
|
54
|
+
|
|
55
|
+
`office-pair.mjs` runs the machine side; every subcommand refuses an
|
|
56
|
+
unattended run. The user runs it in their own terminal (`! node
|
|
57
|
+
~/.claude/scripts/office-pair.mjs start`), so nothing it shows lands in a
|
|
58
|
+
transcript.
|
|
59
|
+
|
|
60
|
+
1. `start [--relay <origin>]` creates the machine key pair (P-256) on first
|
|
61
|
+
use and a relay token (256 bits) kept in `<phone dir>/remote.json` (0600),
|
|
62
|
+
a pairing id and a one-time secret (128 bits, valid 10 minutes). The relay
|
|
63
|
+
origin comes from `prefs.global.office.remote.relayUrl` or the flag, never
|
|
64
|
+
from the code; it is https, or http on the loopback. The relay receives the
|
|
65
|
+
pairing id, the machine public key, the expiry and the SHA-256 of the relay
|
|
66
|
+
token, never the token itself.
|
|
67
|
+
2. The QR code is drawn by the dependency-free encoder in `lib/qr-code.mjs`
|
|
68
|
+
into a 0600 page (`default-src 'none'`) in a 0700 directory and opened in
|
|
69
|
+
the local browser; the secret is never printed. It holds
|
|
70
|
+
`<origin>/office/pair#p=<pairing id>&s=<secret>&k=<machine public key>`.
|
|
71
|
+
A browser never sends the fragment to a server, so the relay neither sees
|
|
72
|
+
the secret nor can swap the machine key.
|
|
73
|
+
3. The phone, signed in, scans it, creates its Ed25519 device key and its P-256
|
|
74
|
+
session key, registers a passkey (WebAuthn `create`, user verification
|
|
75
|
+
required) and sends, sealed with a key that mixes the ECDH secret with the
|
|
76
|
+
pairing secret: the device name and public key, and the passkey credential
|
|
77
|
+
id, public key (`getPublicKey()`, SPKI), rp id and origin.
|
|
78
|
+
4. `wait` polls the relay with the token, opens the message, refuses a passkey
|
|
79
|
+
whose origin is not the relay origin or whose rp id is not its host, and
|
|
80
|
+
prints the six-digit code derived from both public keys and a fingerprint
|
|
81
|
+
of the machine key. The phone shows the same code and fingerprint, and
|
|
82
|
+
accepts the pairing only after its user confirms on the phone that both
|
|
83
|
+
match, so a link someone else registered cannot pair the phone with their
|
|
84
|
+
machine.
|
|
85
|
+
5. `confirm <code>` takes the code as the phone shows it. A mismatch drops the
|
|
86
|
+
pairing (one attempt). A match enrols the device with
|
|
87
|
+
`phone-devices.mjs add` (scopes `read,answer`; `launch` stays a separate
|
|
88
|
+
local switch), stores the session key and passkey beside it, and tells the
|
|
89
|
+
relay to start delivering for the pairing. That call carries the first
|
|
90
|
+
machine-to-phone frame (counter 1), sealed with the pairing's keys, holding
|
|
91
|
+
the device id the phone signs its requests with; the relay cannot alter it.
|
|
92
|
+
6. Revoke: `phone-devices.mjs revoke <id>` on the machine also drops the
|
|
93
|
+
device's remote keys; the site can stop delivering for a pairing. Either
|
|
94
|
+
alone ends control.
|
|
95
|
+
|
|
96
|
+
## Transport
|
|
97
|
+
|
|
98
|
+
- The machine client polls the relay over HTTPS (long poll, 25 s) and posts
|
|
99
|
+
responses. No inbound port, no tunnel, no funnel.
|
|
100
|
+
- A frame is `{method, target, headers, body}` (request) or
|
|
101
|
+
`{status, headers, body}` (response), as `phone-api.md` specifies, sealed
|
|
102
|
+
with AES-256-GCM. The keys come from ECDH between the phone session key and
|
|
103
|
+
the machine key, through HKDF-SHA-256 with the pairing id as salt, one key
|
|
104
|
+
per direction; the nonce is a per-direction counter, and a frame whose
|
|
105
|
+
counter does not exceed the last one accepted is dropped.
|
|
106
|
+
- The relay stores `{pairingId, direction, counter, ciphertext, expiresAt}` in
|
|
107
|
+
a table only the service role reaches, and deletes a row on delivery or
|
|
108
|
+
after 10 minutes.
|
|
109
|
+
- The machine client takes a target only in the canonical form the local
|
|
110
|
+
server routes on (no encoded dot segment, backslash or fragment), so the
|
|
111
|
+
tier and the route prefix are judged on the path the server executes. Only
|
|
112
|
+
one client runs at a time (a lock file), and each poll writes its counters
|
|
113
|
+
onto the config as it is on disk, so a revoke made meanwhile is kept.
|
|
114
|
+
- The machine client forwards only targets under `/v1/phone/`, never adds an
|
|
115
|
+
`Authorization` header, and rewrites `Host` to the loopback address; the
|
|
116
|
+
local server's own checks then apply unchanged.
|
|
117
|
+
- Remote is off by default: the client runs only while the office is on and
|
|
118
|
+
a device is paired; `office-off` stops it.
|
|
119
|
+
|
|
120
|
+
## Passkey tiers
|
|
121
|
+
|
|
122
|
+
The device signature proves which phone sent a request; the passkey proves the
|
|
123
|
+
user was holding it. Each phone route carries a tier from the route table:
|
|
124
|
+
|
|
125
|
+
| Tier | Phone routes | Passkey |
|
|
126
|
+
|---|---|---|
|
|
127
|
+
| read | `GET /v1/phone/runs`, `GET /v1/phone/runs/{id}` | one at session open; the session locks after 5 minutes idle |
|
|
128
|
+
| low | `POST /v1/phone/runs/{id}/answer`, a canned steer | within 2 minutes of the last passkey (configurable down to every time) |
|
|
129
|
+
| high | `POST /v1/phone/launch` and any later route that changes code or writes to a host | every time, bound to the request |
|
|
130
|
+
|
|
131
|
+
For a passkey-bound request the phone sends `X-MA-Passkey` with a WebAuthn
|
|
132
|
+
assertion whose challenge is SHA-256 of the request's canonical signed text.
|
|
133
|
+
The machine verifies, before the request reaches the local server: the rp id
|
|
134
|
+
hash and origin match the paired ones, the user verified flag is set, the
|
|
135
|
+
signature verifies with the paired credential key, and the sign count (when the
|
|
136
|
+
authenticator keeps one) has grown. Steering from the phone is a canned
|
|
137
|
+
template chosen by id and expanded on the machine; free-text steering, widening
|
|
138
|
+
actions (autopilot on, stack, routine delete, host permissions) and merges stay
|
|
139
|
+
desktop routes.
|
|
140
|
+
|
|
141
|
+
## Audit
|
|
142
|
+
|
|
143
|
+
The local server keeps its per-request audit as today. The machine client adds
|
|
144
|
+
`<phone dir>/remote-frames.jsonl` (0600): time, device, counter,
|
|
145
|
+
target path without query, tier, passkey verdict and the reason of a drop.
|
|
146
|
+
Never a body, a header value or a signature.
|
|
147
|
+
|
|
148
|
+
## Decisions
|
|
149
|
+
|
|
150
|
+
1. **The machine client ships in the public pipeline**, inert until
|
|
151
|
+
`office-pair` pairs a device. The relay URL comes from prefs or the pairing
|
|
152
|
+
QR, never from the code.
|
|
153
|
+
2. **Steering from the phone takes canned templates only**, chosen by id and
|
|
154
|
+
expanded on the machine.
|
|
155
|
+
3. **The first release serves the owner's account only**; invites follow as a
|
|
156
|
+
separate step.
|
|
157
|
+
4. **The relay queue is a short-lived table in the site's database**: ciphertext
|
|
158
|
+
only, deleted on delivery or after 10 minutes, service role only.
|
|
159
|
+
|
|
160
|
+
## To verify on a real phone
|
|
161
|
+
|
|
162
|
+
WebCrypto Ed25519 and non-extractable P-256 keys in the phone's browser,
|
|
163
|
+
`getPublicKey()` on the passkey registration response, and the long-poll
|
|
164
|
+
interval against the site's function time limit. The design falls back to
|
|
165
|
+
P-256 ECDSA for the device key if Ed25519 is missing, which `_phone-auth.mjs`
|
|
166
|
+
would then accept as a second key type.
|
|
@@ -66,7 +66,7 @@ not mitigated here.
|
|
|
66
66
|
| T20 | A learned statement carrying a secret or a machine path to the page | `GET /v1/memory` returns counts, the code graph's age and size, and the five newest learning statements per repository, each passed through the transcript redactor (provider credential rules, paths and addresses) and cut to 120 characters. Repository names are matched against a safe character set before any file is opened. Read only; `local-only` | `office-routes.test.mjs` "GET /v1/memory reports every shelf, redacted" |
|
|
67
67
|
| T21 | Machine details through the resource panel | `GET /v1/system` returns numbers only (cores, load, memory, free disk, process counts, log folder size) and two fixed warnings; no path, process argument, user or host name. The schema allows nothing else. The measurement runs as child processes off the event loop, concurrent requests share one measurement, and the log folder size is reused for a minute. Readable from a phone, since it names nothing | `office-routes.test.mjs` "GET /v1/system returns numbers only: no path, user or host name" |
|
|
68
68
|
| T22 | Old run logs deleted wider than asked, or a live run's log removed | `POST /v1/prune-logs` takes only an age from a fixed set (7, 30 or 90 days) and runs `prune-logs.sh`, the script `/multi-agent:prune-logs` runs: the dry run is the preview; the confirmed step lists the folders again, refuses with `changed` when they differ, and then prunes only the previewed list through `--only-from`, under a token bound to that age and list. The script never touches the audit trail, the metrics corpus or the task counters, refuses a log root that is `/`, the home folder or a git checkout, and spares a folder written in the last minutes and a run that can still be resumed. Tagged `local-only`/`high` | `office-routes.test.mjs` "POST /v1/prune-logs previews, then prunes only on the token" |
|
|
69
|
-
| T23 | Pull request listing exposing a token, or the native task mirror writing into another run | Each GitHub account gh is logged in to is asked with `gh auth token` for that host and login; the token travels in `GH_TOKEN` (and `GH_ENTERPRISE_TOKEN` for an Enterprise Server host) for the child process only, never on a command line, in a response or a log. Bitbucket Server is read over https only (a plain-http host is refused unless it is the loopback), redirects are not followed, and the token, read from the credential store with a deadline, travels in the `Authorization` header only; an unreachable host is a reason on that source, not a failure of the list. A redacted answer drops URLs and logins, numbering the accounts instead. The task-mirror hook (PostToolUse on TaskCreate and TaskUpdate) writes only into the run whose recorded session is the calling session, skips phase tiles, never prints, and exits 0 on any input it cannot read | `work-items-prs.test.mjs`, `task-mirror.test.mjs` |
|
|
69
|
+
| T23 | Pull request listing exposing a token, or the native task mirror writing into another run | Each GitHub account gh is logged in to is asked with `gh auth token` for that host and login; the token travels in `GH_TOKEN` (and `GH_ENTERPRISE_TOKEN` for an Enterprise Server host) for the child process only, never on a command line, in a response or a log. Bitbucket Server is read over https only (a plain-http host is refused unless it is the loopback), redirects are not followed, and the token, read from the credential store with a deadline, travels in the `Authorization` header only; an unreachable host is a reason on that source, not a failure of the list. `GET /v1/prs/reach` (local-only) asks gh for the changed file paths of at most 20 GitHub pull requests, three at a time, each with its account's token in the environment; it asks nothing for a repository whose name is not `owner/repo` or that has no graph under `~/.claude/knowledge/<repo name>/`. The paths serve only as keys into the local graph: nothing is read from disk by them and none is returned, only counts. The graph is parsed in a child process, so no other route waits behind it. A redacted answer drops URLs and logins, numbering the accounts instead. The task-mirror hook (PostToolUse on TaskCreate and TaskUpdate) writes only into the run whose recorded session is the calling session, skips phase tiles, never prints, and exits 0 on any input it cannot read | `work-items-prs.test.mjs`, `task-mirror.test.mjs`, `pr-reach.test.mjs`, `office-work.test.mjs` "GET /v1/prs/reach says which pull requests have no graph to measure against" |
|
|
70
70
|
| T24 | The panel changing a preference wider than the terminal would, or posting addresses to a phone | `POST /v1/settings` takes one of three settings and one of its two values, previews the change and runs, under a token bound to that setting and value, what `/multi-agent:language`, `/multi-agent:model` or `/multi-agent:route-on`/`route-off` runs; route on passes the saved strategy back so only the switch changes, nothing the panel sends can add rules or a free provider, and two changes never run at once. Tagged `local-only`/`high`. Report links: `channelResults` URLs must be https, travel only in the run record's `channelUrls` (local, dropped from a redacted answer) and are shown as text, never followed | `office-routes.test.mjs` "POST /v1/settings changes a switch only on the token, through its script", `runs-index.test.mjs` |
|
|
71
71
|
|
|
72
72
|
## Residual risks
|
|
@@ -29,7 +29,22 @@ git -C "$WORKTREE" diff --name-only "origin/$BASE_BRANCH"...HEAD \
|
|
|
29
29
|
| node $HOME/.claude/scripts/scope-check-gate.mjs --check "$WORKTREE/.pipeline/scope-check.json" --diff-files -
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
Output `{ok, justified, unjustified[], unlisted[], notDone}`, plus `formatOnly[]` with `--patch` / `--patch-range` (`patchError` when the diff could not be read). With `--patch <unified diff>` (or `--repo <dir> --patch-range <a>...<b>`, which reads that diff itself), `formatOnly[]` lists hunks whose removed and added lines differ only in whitespace: a reformat of lines the task did not need to touch. It is reported, never fatal, and reviewers see it in `<scope-self-check>`. Exit 1 lists `unjustified[]` (in the diff, no reason or an empty one); `unlisted[]` (in the record, not in the diff) is reported and never fatal. Complete the record once and re-run. A second exit 1 does not block: log `dev.scope_check=incomplete unjustified=<n>` and hand off; Phase 4 receives the gate output inside `<scope-self-check>`, so reviewers see which files arrived without a stated reason. `--advisory` (used by Phase 4) always exits 0. Progress line: ` → checking scope self-check ({n} files, {m} not done)`.
|
|
32
|
+
Output `{ok, justified, unjustified[], unlisted[], notDone}`, plus `formatOnly[]` with `--patch` / `--patch-range` (`patchError` when the diff could not be read). With `--patch <unified diff>` (or `--repo <dir> --patch-range <a>...<b>`, which reads that diff itself), `formatOnly[]` lists hunks whose removed and added lines differ only in whitespace: a reformat of lines the task did not need to touch. It is reported, never fatal, and reviewers see it in `<scope-self-check>`. On a rework pass, `--rejected <triage-output.json> --repo <dir> --rework-base <rev>` (the two come together) adds `rejectedTouched[]` (`rejectedError` when unreadable): each finding triage rejected whose line a hunk of `git diff <rework base>` against the working tree changed, read on the hunk's old side within two lines. Phase 2 acts on accepted findings only, so such a hunk is a fix triage ruled out; it is reported, never fatal. Exit 1 lists `unjustified[]` (in the diff, no reason or an empty one); `unlisted[]` (in the record, not in the diff) is reported and never fatal. Complete the record once and re-run. A second exit 1 does not block: log `dev.scope_check=incomplete unjustified=<n>` and hand off; Phase 4 receives the gate output inside `<scope-self-check>`, so reviewers see which files arrived without a stated reason. `--advisory` (used by Phase 4) always exits 0. Progress line: ` → checking scope self-check ({n} files, {m} not done)`.
|
|
33
|
+
|
|
34
|
+
## Rework pass
|
|
35
|
+
|
|
36
|
+
Rejected findings carry the line numbers of the tree the review read, and every round rewrites `triage-output.json`, so a rework is compared against that round's own base.
|
|
37
|
+
|
|
38
|
+
1. Before the first rework edit, record the base: `node $HOME/.claude/scripts/rework-base.mjs record --worktree "$WORKTREE" --iteration "$(jq '.reviewIterations | length' "$STATE_FILE")"`. One file per round, `.pipeline/rework-base-<N>`; a resumed re-entry of the same round keeps the base it started from.
|
|
39
|
+
2. In Step 3.7, after the normal gate:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
REWORK_BASE=$(node $HOME/.claude/scripts/rework-base.mjs get --worktree "$WORKTREE" --iteration "$(jq '.reviewIterations | length' "$STATE_FILE")")
|
|
43
|
+
node $HOME/.claude/scripts/scope-check-gate.mjs --check "$WORKTREE/.pipeline/scope-check.json" --diff-files /dev/null --advisory \
|
|
44
|
+
--rejected "$WORKTREE/triage-output.json" --repo "$WORKTREE" --rework-base "$REWORK_BASE"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Each `rejectedTouched[]` entry is an edit to lines triage ruled out: revert that hunk unless an accepted finding needs it, and say which in the record's reason. Log `dev.rework_rejected_touch=<n>`.
|
|
33
48
|
|
|
34
49
|
## Known limits in the code
|
|
35
50
|
|
|
@@ -21,6 +21,16 @@
|
|
|
21
21
|
|
|
22
22
|
Downgrades go to `deferred`, never `rejected`: triage judged the issue real, and deferred items surface in the Phase 5 report for a human eye.
|
|
23
23
|
|
|
24
|
+
## Repeat runs
|
|
25
|
+
|
|
26
|
+
Phase 2's stability rule keeps each run of the new and changed tests as `.pipeline/repeat-<k>.junit.xml` (k = 1..N) and judges them together:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
node $HOME/.claude/scripts/test-stability-gate.mjs --dir "$WORKTREE/.pipeline" --expect N
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Exit 1 means a missing run, a flake (`flaky[]`: outcomes that differ across runs) or a test that failed every time; the step is not GREEN. A runner that writes no JUnit XML (xcodebuild) has each run exported with `xcrun xcresulttool` first, or logs `test.stability=unverifiable` and says so in the Phase 5 report.
|
|
33
|
+
|
|
24
34
|
## Red-test handoff to Phase 3
|
|
25
35
|
|
|
26
36
|
`state.reviewIterations[-1].verifyByTest = { attempted, confirmed, downgraded, inconclusive, redTests: [{file, testRef, issue}] }`. On the Phase 3 re-entry, each `redTests[]` entry IS the RED step of the rework TDD cycle for its finding: the reflection prompt instructs "a failing repro test already exists at <testRef>; make it green; do not delete or weaken it." The dev agent must not write a duplicate failing test for those findings.
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
## Keychain Token Registry
|
|
2
2
|
|
|
3
|
+
<!-- toc -->
|
|
4
|
+
- [Keychain Token Registry](#keychain-token-registry)
|
|
5
|
+
- [Rule 1 - Never prompt for a token *value* mid-run](#rule-1---never-prompt-for-a-token-value-mid-run)
|
|
6
|
+
- [Rule 2 - MUST: never ask the user for what a mapped credential can fetch (BLOCKING)](#rule-2---must-never-ask-the-user-for-what-a-mapped-credential-can-fetch-blocking)
|
|
7
|
+
- [Credential store mechanics](#credential-store-mechanics)
|
|
8
|
+
<!-- /toc -->
|
|
9
|
+
|
|
3
10
|
> **Pickers follow** `$HOME/.claude/multi-agent-refs/picker-contract.md`: never a one-option call, and branch on the option selected, not on its text.
|
|
4
11
|
|
|
5
12
|
Tokens live in the macOS Keychain. Key names are resolved via **preferences mapping** - never hardcoded.
|