@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mmerterden/multi-agent-pipeline",
|
|
3
|
-
"version": "20.
|
|
3
|
+
"version": "20.35.0",
|
|
4
4
|
"description": "6-phase AI development pipeline with full orchestration on Claude Code, Copilot CLI and Codex CLI. Analysis, planning, TDD, CLI-aware parallel review with consensus surfacing + Fable triage, default-FAIL evidence gates, secret + intent guards, per-phase cost ledger, persistent learnings memory, wiki generation, commit automation. Token-preserving uninstall.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -33,6 +33,9 @@ number you report is a number the caller can open directly.
|
|
|
33
33
|
restated as one region.
|
|
34
34
|
- If you did not see the whole file, set `truncated: true`. Do not summarize a
|
|
35
35
|
part as though it were the whole.
|
|
36
|
+
- **A script reads the answer, not a person.** Return the JSON object and nothing
|
|
37
|
+
around it, and write `why` as a short phrase. Shorten the wording, never the
|
|
38
|
+
content: a negation, a number or a path stays exactly as found.
|
|
36
39
|
|
|
37
40
|
## Output Format
|
|
38
41
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: "Multi-channel reporter - Jira/Confluence/Wiki/PR description/Board status. Multi-select channels + content, humanizer pass, reviewer-preserving Bitbucket PR PUT, GitHub Projects v2 board status move.
|
|
2
|
+
description: "Multi-channel reporter - Jira/Confluence/Wiki/PR description/Board status. Multi-select channels + content, humanizer pass, reviewer-preserving Bitbucket PR PUT, GitHub Projects v2 board status move. Use to post or redo a run's report; Phase 5 delegates here, or invoke by Jira ID / PR URL / branch."
|
|
3
3
|
description-tr: "Çok kanallı raporlayıcı - Jira/Confluence/Wiki/PR açıklaması/Board durumu. Çoklu kanal + içerik seçimi, humanizer geçişi, reviewer'ı koruyan Bitbucket PR PUT, GitHub Projects v2 board durum taşıma. Faz 5 bu skill'e delege eder; Jira ID / PR URL / branch ile sonradan da çağrılabilir."
|
|
4
4
|
argument-hint: '[Jira-id | PR-url | #N | Jira-url] [--channels pr,jira,confluence,wiki,board] [--content normal,technical,test,auto-diff,note,test-run] [--flow-run <runId>] [--message "..."] [--message-file path] [--board-status <key>] [--dry-run] [--append] [--ready]'
|
|
5
5
|
parameters: [{"name":"target","kind":"text"},{"name":"--channels","kind":"enum","repeat":true,"enum":["pr","jira","confluence","wiki","board"]},{"name":"--content","kind":"enum","repeat":true,"enum":["normal","technical","test","auto-diff","note","test-run"]},{"name":"--flow-run","kind":"text"},{"name":"--message","kind":"text"},{"name":"--message-file","kind":"path"},{"name":"--board-status","kind":"text"},{"name":"--dry-run","kind":"flag"},{"name":"--append","kind":"flag"},{"name":"--ready","kind":"flag"}]
|
|
@@ -46,6 +46,10 @@ fresh bearer token per start). This command and `office-off` /
|
|
|
46
46
|
`task-mirror` hook that install registers for every run.
|
|
47
47
|
- The panel's writes call the same scripts the terminal commands call; it adds
|
|
48
48
|
no behaviour of its own.
|
|
49
|
-
- The server listens on 127.0.0.1 only. Remote access is
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
- The server listens on 127.0.0.1 only. Remote access is off unless a phone
|
|
50
|
+
was paired: then `office-on` also starts `office-remote-client.mjs`, which
|
|
51
|
+
polls the relay over HTTPS (no inbound port) and stops with the server
|
|
52
|
+
(`features/office-remote-protocol.md`). `office-status` says whether a
|
|
53
|
+
device is paired. Pairing is the user's own step in their terminal, never
|
|
54
|
+
run from here: `! node ~/.claude/scripts/office-pair.mjs start`, then
|
|
55
|
+
`wait`, then `confirm <code the phone shows>`.
|
|
@@ -18,8 +18,9 @@ node "$HOME/.claude/scripts/office-control.mjs" status --json
|
|
|
18
18
|
Report in `outputLanguage`:
|
|
19
19
|
|
|
20
20
|
- `running`, and when it is, `url` and `idleMinutes` (0 means it runs until `office-off`);
|
|
21
|
-
- `remote`: "
|
|
22
|
-
|
|
21
|
+
- `remote`: "off" until a phone is paired with `office-pair.mjs`, "configured"
|
|
22
|
+
when only the prefs block is set, "paired" once a device is; report the line
|
|
23
|
+
as given;
|
|
23
24
|
- `pairedDevices`: the devices the phone registry lists; revoking one is
|
|
24
25
|
`phone-devices.mjs revoke`;
|
|
25
26
|
- `recentActions`: the last writes taken through the server (route, step,
|
|
@@ -3,6 +3,15 @@
|
|
|
3
3
|
Versions of the client contract (`manifest.json` `contractVersion`), separate
|
|
4
4
|
from the package version. See `README.md` for what counts as minor and major.
|
|
5
5
|
|
|
6
|
+
## 1.7.0
|
|
7
|
+
|
|
8
|
+
Added. Nothing existing changes shape.
|
|
9
|
+
|
|
10
|
+
- `GET /v1/prs/reach` (`work-items.mjs reach`, local-only): for each open GitHub
|
|
11
|
+
pull request (at most 20), how many files in the repository's own code graph
|
|
12
|
+
depend on what it changes, widest first, with a reason when it cannot be
|
|
13
|
+
measured. Changed paths are never returned, only counts.
|
|
14
|
+
|
|
6
15
|
## 1.6.0
|
|
7
16
|
|
|
8
17
|
Added. Nothing existing changes shape.
|
|
@@ -71,6 +71,7 @@ same bytes (`test/contract-parity.test.mjs`):
|
|
|
71
71
|
| Post a review verdict (two steps) | `work-items.mjs verdict ID [--post]` | `POST /v1/runs/{id}/review-verdict` |
|
|
72
72
|
| An issue's maturity | `work-items.mjs maturity KEY` | `GET /v1/issues/{key}/maturity` |
|
|
73
73
|
| Open pull requests | `work-items.mjs prs` | `GET /v1/prs` |
|
|
74
|
+
| How far each open pull request reaches in its repository's graph | `work-items.mjs reach` | `GET /v1/prs/reach` |
|
|
74
75
|
| Toolkit MCP tools | `toolkit-tools.mjs list` | `GET /v1/tools` |
|
|
75
76
|
| Call an allowed toolkit tool (read-only, no location argument) | `toolkit-tools.mjs call NAME JSON` | `POST /v1/tools/{name}/call` |
|
|
76
77
|
| A Confluence page, a Crashlytics issue, a Figma frame | `service-read.mjs confluence\|crashlytics\|figma ...` | `GET /v1/services/{confluence,crashlytics,figma}` |
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"contractVersion": "1.
|
|
2
|
+
"contractVersion": "1.7.0",
|
|
3
3
|
"supportedMajors": [1],
|
|
4
4
|
"description": "The pipeline's client contract: every surface a client reads or writes, the CLI invocation and the HTTP route that carry it, and the schema its JSON follows. Schemas are not copied here; schemasDir points at the one tree the producers validate against. Paths in cli are relative to the pipeline directory (installed: ~/.claude).",
|
|
5
5
|
"schemasDir": "../schemas",
|
|
@@ -100,6 +100,7 @@
|
|
|
100
100
|
"run-evidence.schema.json",
|
|
101
101
|
"issue-maturity.schema.json",
|
|
102
102
|
"prs.schema.json",
|
|
103
|
+
"pr-reach.schema.json",
|
|
103
104
|
"review-verdict-request.schema.json",
|
|
104
105
|
"review-verdict.schema.json",
|
|
105
106
|
"tools.schema.json",
|
|
@@ -745,6 +746,18 @@
|
|
|
745
746
|
"remote": "read",
|
|
746
747
|
"risk": "read"
|
|
747
748
|
},
|
|
749
|
+
{
|
|
750
|
+
"id": "prs-reach",
|
|
751
|
+
"method": "GET",
|
|
752
|
+
"path": "/v1/prs/reach",
|
|
753
|
+
"query": [],
|
|
754
|
+
"cli": ["node", "scripts/work-items.mjs", "reach"],
|
|
755
|
+
"schema": "pr-reach.schema.json",
|
|
756
|
+
"version": "1.1.0",
|
|
757
|
+
"note": "Run as a child process so parsing a large graph never holds up other routes. At most 20 GitHub pull requests, three gh calls at a time, each with its account's token in GH_TOKEN. A repository with no graph under ~/.claude/knowledge/<repo name>/ is listed with its reason and costs no gh call.",
|
|
758
|
+
"remote": "local-only",
|
|
759
|
+
"risk": "read"
|
|
760
|
+
},
|
|
748
761
|
{
|
|
749
762
|
"id": "tools",
|
|
750
763
|
"method": "GET",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Generated by pipeline/contract/build.mjs from pipeline/schemas/*.schema.json.
|
|
2
2
|
// Do not edit: change the schema and run `node pipeline/contract/build.mjs --write`.
|
|
3
3
|
|
|
4
|
-
export type ContractVersion = "1.
|
|
5
|
-
export type SurfaceId = "runs" | "run" | "commands" | "questions" | "issues" | "worktrees" | "launch-spec" | "repos" | "repo-add" | "repo-remove" | "launch" | "answer" | "run-log" | "resume" | "kill" | "gc" | "autopilot" | "autopilot-off" | "phone-runs" | "phone-run" | "phone-answer" | "phone-launch" | "autopilot-on" | "steer" | "activity" | "activity-stream" | "agent-chat" | "routines" | "routine-delete" | "jobs" | "job-start" | "stack" | "stack-set" | "ledger" | "system" | "prune-logs" | "settings" | "settings-set" | "credentials" | "memory" | "run-results" | "run-evidence" | "review-verdict" | "issue-maturity" | "prs" | "tools" | "tool-call" | "service-confluence" | "service-crashlytics" | "service-figma" | "permissions";
|
|
4
|
+
export type ContractVersion = "1.7.0";
|
|
5
|
+
export type SurfaceId = "runs" | "run" | "commands" | "questions" | "issues" | "worktrees" | "launch-spec" | "repos" | "repo-add" | "repo-remove" | "launch" | "answer" | "run-log" | "resume" | "kill" | "gc" | "autopilot" | "autopilot-off" | "phone-runs" | "phone-run" | "phone-answer" | "phone-launch" | "autopilot-on" | "steer" | "activity" | "activity-stream" | "agent-chat" | "routines" | "routine-delete" | "jobs" | "job-start" | "stack" | "stack-set" | "ledger" | "system" | "prune-logs" | "settings" | "settings-set" | "credentials" | "memory" | "run-results" | "run-evidence" | "review-verdict" | "issue-maturity" | "prs" | "prs-reach" | "tools" | "tool-call" | "service-confluence" | "service-crashlytics" | "service-figma" | "permissions";
|
|
6
6
|
|
|
7
7
|
/** What `runs-index.mjs --json` prints: every run under the log root and the unattended log root (one record per task id), grouped per status/SKILL.md 3b. Fields are only ever added - a decoder ignores keys it does not know. `--redact` drops every x-sensitivity local field (and `logsRoot`) and scrubs the free text in the title, `now`, sub-step names, task subjects and the pending question. */
|
|
8
8
|
export interface RunsIndex {
|
|
@@ -1202,6 +1202,28 @@ export interface Prs {
|
|
|
1202
1202
|
}>;
|
|
1203
1203
|
}
|
|
1204
1204
|
|
|
1205
|
+
/** What GET /v1/prs/reach returns (`work-items.mjs reach`): for each open GitHub pull request (at most 20), how many files in the repository's own code graph depend on what it changes, widest first. Changed paths serve only as keys into the local graph and are never returned. A pull request that cannot be measured carries reach null and its reason. */
|
|
1206
|
+
export interface PrReach {
|
|
1207
|
+
contractVersion: string;
|
|
1208
|
+
/** Reverse-dependency depth the count was taken at. */
|
|
1209
|
+
depth: number;
|
|
1210
|
+
items: Array<{
|
|
1211
|
+
source: "github" | "bitbucket";
|
|
1212
|
+
repo: string;
|
|
1213
|
+
number: number;
|
|
1214
|
+
/** The knowledge-folder project whose graph answered, or null. */
|
|
1215
|
+
project: string | null;
|
|
1216
|
+
/** Files the pull request changes. */
|
|
1217
|
+
changed?: number;
|
|
1218
|
+
/** Of those, files the graph knows. */
|
|
1219
|
+
inGraph?: number;
|
|
1220
|
+
/** Files that depend on the changed ones, within depth. Null when not measured. */
|
|
1221
|
+
reach: number | null;
|
|
1222
|
+
/** Why reach is null. */
|
|
1223
|
+
reason?: string;
|
|
1224
|
+
}>;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1205
1227
|
/** The body of POST /v1/runs/{id}/review-verdict: empty for the preview, confirmToken to post. The verdict is never chosen by the request; it follows from the review's recorded findings. */
|
|
1206
1228
|
export interface ReviewVerdictRequest {
|
|
1207
1229
|
contractVersion: string;
|
|
@@ -18,9 +18,12 @@
|
|
|
18
18
|
|
|
19
19
|
import {
|
|
20
20
|
chmodSync,
|
|
21
|
+
closeSync,
|
|
22
|
+
fstatSync,
|
|
21
23
|
linkSync,
|
|
22
24
|
lstatSync,
|
|
23
25
|
mkdirSync,
|
|
26
|
+
openSync,
|
|
24
27
|
readFileSync,
|
|
25
28
|
readlinkSync,
|
|
26
29
|
realpathSync,
|
|
@@ -47,22 +50,70 @@ export class FileLockTimeout extends Error {
|
|
|
47
50
|
|
|
48
51
|
const pause = (ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
49
52
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
/**
|
|
54
|
+
* The PID inside a lock file and that file's identity, read through one
|
|
55
|
+
* descriptor. Read by path twice, the PID can come from a lock that was
|
|
56
|
+
* released in between and the identity from the one acquired after it, which
|
|
57
|
+
* makes a live lock look like a dead holder's.
|
|
58
|
+
*
|
|
59
|
+
* @param {string} lockPath
|
|
60
|
+
* @returns {{pid: number, ino: number, mtimeMs: number} | null}
|
|
61
|
+
*/
|
|
62
|
+
export function readLockOwner(lockPath) {
|
|
63
|
+
let fd;
|
|
53
64
|
try {
|
|
54
|
-
|
|
55
|
-
st =
|
|
65
|
+
fd = openSync(lockPath, "r");
|
|
66
|
+
const st = fstatSync(fd);
|
|
67
|
+
const pid = parseInt(readFileSync(fd, "utf8").trim(), 10);
|
|
68
|
+
return { pid, ino: st.ino, mtimeMs: st.mtimeMs };
|
|
56
69
|
} catch {
|
|
57
70
|
return null;
|
|
71
|
+
} finally {
|
|
72
|
+
if (fd !== undefined) closeSync(fd);
|
|
58
73
|
}
|
|
59
|
-
|
|
60
|
-
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Remove the lock judged stale, and only that one. The lock is first moved to
|
|
78
|
+
* a name private to this process, so the file examined is the file removed; a
|
|
79
|
+
* live lock that took its place in the meantime is linked back as the same
|
|
80
|
+
* file, which its holder's release recognises by inode.
|
|
81
|
+
*
|
|
82
|
+
* @param {string} lockPath
|
|
83
|
+
* @param {{ino: number, mtimeMs: number}} judged
|
|
84
|
+
* @returns {boolean} true when the stale lock was removed
|
|
85
|
+
*/
|
|
86
|
+
export function reapLock(lockPath, judged) {
|
|
87
|
+
const reap = `${lockPath}.${process.pid}.reap`;
|
|
61
88
|
try {
|
|
62
|
-
|
|
89
|
+
renameSync(lockPath, reap);
|
|
90
|
+
} catch {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
const st = statSync(reap);
|
|
94
|
+
if (st.ino === judged.ino && st.mtimeMs === judged.mtimeMs) {
|
|
95
|
+
unlinkSync(reap);
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
linkSync(reap, lockPath);
|
|
100
|
+
} catch {
|
|
101
|
+
/* a new holder already has the path; the displaced one keeps its inode */
|
|
102
|
+
}
|
|
103
|
+
unlinkSync(reap);
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function staleIdentity(lockPath, staleMs) {
|
|
108
|
+
const owner = readLockOwner(lockPath);
|
|
109
|
+
if (!owner) return null;
|
|
110
|
+
const age = Date.now() - owner.mtimeMs;
|
|
111
|
+
if (!Number.isInteger(owner.pid) || owner.pid <= 0) return age > staleMs ? owner : null;
|
|
112
|
+
try {
|
|
113
|
+
process.kill(owner.pid, 0);
|
|
63
114
|
return null;
|
|
64
115
|
} catch (err) {
|
|
65
|
-
return err.code === "ESRCH" ?
|
|
116
|
+
return err.code === "ESRCH" ? owner : null;
|
|
66
117
|
}
|
|
67
118
|
}
|
|
68
119
|
|
|
@@ -82,12 +133,7 @@ function acquire(lockPath, timeoutMs, staleMs) {
|
|
|
82
133
|
}
|
|
83
134
|
const stale = staleIdentity(lockPath, staleMs);
|
|
84
135
|
if (stale) {
|
|
85
|
-
|
|
86
|
-
const now = statSync(lockPath);
|
|
87
|
-
if (now.ino === stale.ino && now.mtimeMs === stale.mtimeMs) unlinkSync(lockPath);
|
|
88
|
-
} catch {
|
|
89
|
-
/* gone already */
|
|
90
|
-
}
|
|
136
|
+
reapLock(lockPath, stale);
|
|
91
137
|
continue;
|
|
92
138
|
}
|
|
93
139
|
if (Date.now() >= deadline) throw new FileLockTimeout(lockPath, timeoutMs);
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* office-remote.mjs - the machine side of the office remote layer
|
|
3
|
+
* (features/office-remote-protocol.md).
|
|
4
|
+
*
|
|
5
|
+
* - Keys: P-256 ECDH between the machine key and the phone's session key,
|
|
6
|
+
* through HKDF-SHA-256 with the pairing id as salt, into one AES-256 key per
|
|
7
|
+
* direction.
|
|
8
|
+
* - Frames: AES-256-GCM; the 12-byte nonce is a 4-byte direction tag and an
|
|
9
|
+
* 8-byte counter, so a frame opens only in its own direction and a counter
|
|
10
|
+
* that does not exceed the last accepted one is refused as a replay.
|
|
11
|
+
* - Pairing: the phone's first message is sealed with a key that also mixes in
|
|
12
|
+
* the one-time pairing secret from the QR code, so the relay cannot open it
|
|
13
|
+
* even holding both public keys.
|
|
14
|
+
* - Comparison code: six digits from both public keys, the same on either end.
|
|
15
|
+
* - Passkey: a WebAuthn assertion checked against the paired credential key,
|
|
16
|
+
* rp id, origin, the request-bound challenge, user verification and the sign
|
|
17
|
+
* count.
|
|
18
|
+
*
|
|
19
|
+
* Zero runtime dependencies (ADR-0004): node:crypto only.
|
|
20
|
+
*
|
|
21
|
+
* @module pipeline/lib/office-remote
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import {
|
|
25
|
+
createCipheriv,
|
|
26
|
+
createDecipheriv,
|
|
27
|
+
createHash,
|
|
28
|
+
createPrivateKey,
|
|
29
|
+
createPublicKey,
|
|
30
|
+
diffieHellman,
|
|
31
|
+
generateKeyPairSync,
|
|
32
|
+
hkdfSync,
|
|
33
|
+
timingSafeEqual,
|
|
34
|
+
verify,
|
|
35
|
+
} from "node:crypto";
|
|
36
|
+
|
|
37
|
+
const DIRECTIONS = { "phone-to-machine": 1, "machine-to-phone": 2 };
|
|
38
|
+
const INFO = "ma-office v1 ";
|
|
39
|
+
const MAX_COUNTER = 2 ** 48;
|
|
40
|
+
|
|
41
|
+
const b64u = (b) => Buffer.from(b).toString("base64url");
|
|
42
|
+
const fromB64u = (s) => Buffer.from(String(s || ""), "base64url");
|
|
43
|
+
const sha256 = (b) => createHash("sha256").update(b).digest();
|
|
44
|
+
|
|
45
|
+
/** A new machine key pair: PKCS#8 PEM private key, base64url SPKI public key. */
|
|
46
|
+
export function createMachineKeys() {
|
|
47
|
+
const { privateKey, publicKey } = generateKeyPairSync("ec", { namedCurve: "prime256v1" });
|
|
48
|
+
return {
|
|
49
|
+
privatePem: privateKey.export({ type: "pkcs8", format: "pem" }),
|
|
50
|
+
publicKey: b64u(publicKey.export({ type: "spki", format: "der" })),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function shared(privatePem, peerPublic) {
|
|
55
|
+
return diffieHellman({
|
|
56
|
+
privateKey: createPrivateKey(privatePem),
|
|
57
|
+
publicKey: createPublicKey({ key: fromB64u(peerPublic), format: "der", type: "spki" }),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const derive = (ikm, pairingId, label) =>
|
|
62
|
+
Buffer.from(
|
|
63
|
+
hkdfSync("sha256", ikm, Buffer.from(String(pairingId)), Buffer.from(INFO + label), 32),
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The two AES keys of a pairing, from this end's point of view.
|
|
68
|
+
*
|
|
69
|
+
* @param {{privatePem: string, peerPublic: string, pairingId: string, role: "machine"|"phone"}} p
|
|
70
|
+
* @returns {{send: Buffer, recv: Buffer}}
|
|
71
|
+
*/
|
|
72
|
+
export function directionKeys({ privatePem, peerPublic, pairingId, role }) {
|
|
73
|
+
const ikm = shared(privatePem, peerPublic);
|
|
74
|
+
const p2m = derive(ikm, pairingId, "phone-to-machine");
|
|
75
|
+
const m2p = derive(ikm, pairingId, "machine-to-phone");
|
|
76
|
+
return role === "machine" ? { send: m2p, recv: p2m } : { send: p2m, recv: m2p };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function nonceFor(direction, counter) {
|
|
80
|
+
const tag = DIRECTIONS[direction];
|
|
81
|
+
if (!tag) throw new Error(`unknown direction ${direction}`);
|
|
82
|
+
if (!Number.isSafeInteger(counter) || counter < 1 || counter >= MAX_COUNTER) {
|
|
83
|
+
throw new Error("counter out of range");
|
|
84
|
+
}
|
|
85
|
+
const iv = Buffer.alloc(12);
|
|
86
|
+
iv.writeUInt32BE(tag, 0);
|
|
87
|
+
iv.writeUIntBE(counter, 6, 6);
|
|
88
|
+
return iv;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Seal a JSON value as one frame. */
|
|
92
|
+
export function sealFrame(key, counter, direction, value) {
|
|
93
|
+
const iv = nonceFor(direction, counter);
|
|
94
|
+
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
95
|
+
cipher.setAAD(Buffer.from(direction));
|
|
96
|
+
const body = Buffer.concat([cipher.update(JSON.stringify(value), "utf8"), cipher.final()]);
|
|
97
|
+
return { counter, ct: b64u(Buffer.concat([body, cipher.getAuthTag()])) };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Open one frame. Refuses a counter that does not exceed `lastCounter`, a
|
|
102
|
+
* frame for the other direction and any altered byte.
|
|
103
|
+
*
|
|
104
|
+
* @returns {{ok: true, value: unknown, counter: number} | {ok: false, reason: string}}
|
|
105
|
+
*/
|
|
106
|
+
export function openFrame(key, frame, direction, lastCounter) {
|
|
107
|
+
const counter = Number(frame?.counter);
|
|
108
|
+
if (!Number.isSafeInteger(counter) || counter <= Number(lastCounter || 0)) {
|
|
109
|
+
return { ok: false, reason: "counter did not grow (replay or reorder)" };
|
|
110
|
+
}
|
|
111
|
+
let iv;
|
|
112
|
+
try {
|
|
113
|
+
iv = nonceFor(direction, counter);
|
|
114
|
+
} catch (err) {
|
|
115
|
+
return { ok: false, reason: err.message };
|
|
116
|
+
}
|
|
117
|
+
const raw = fromB64u(frame?.ct);
|
|
118
|
+
if (raw.length < 17) return { ok: false, reason: "frame too short" };
|
|
119
|
+
try {
|
|
120
|
+
const decipher = createDecipheriv("aes-256-gcm", key, iv);
|
|
121
|
+
decipher.setAAD(Buffer.from(direction));
|
|
122
|
+
decipher.setAuthTag(raw.subarray(raw.length - 16));
|
|
123
|
+
const text = Buffer.concat([
|
|
124
|
+
decipher.update(raw.subarray(0, raw.length - 16)),
|
|
125
|
+
decipher.final(),
|
|
126
|
+
]);
|
|
127
|
+
return { ok: true, value: JSON.parse(text.toString("utf8")), counter };
|
|
128
|
+
} catch {
|
|
129
|
+
return { ok: false, reason: "frame did not authenticate" };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function pairingKey({ privatePem, peerPublic, secret, pairingId }) {
|
|
134
|
+
const ikm = Buffer.concat([shared(privatePem, peerPublic), fromB64u(secret)]);
|
|
135
|
+
return derive(ikm, pairingId, "pairing");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Seal the phone's pairing message (counter 1, phone to machine). */
|
|
139
|
+
export function sealPairing({ privatePem, peerPublic, secret, pairingId, value }) {
|
|
140
|
+
return sealFrame(
|
|
141
|
+
pairingKey({ privatePem, peerPublic, secret, pairingId }),
|
|
142
|
+
1,
|
|
143
|
+
"phone-to-machine",
|
|
144
|
+
value,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Open the phone's pairing message with the machine key and the pairing secret. */
|
|
149
|
+
export function openPairing({ privatePem, peerPublic, secret, pairingId, sealed }) {
|
|
150
|
+
try {
|
|
151
|
+
const key = pairingKey({ privatePem, peerPublic, secret, pairingId });
|
|
152
|
+
const r = openFrame(key, sealed, "phone-to-machine", 0);
|
|
153
|
+
return r.ok ? { ok: true, value: r.value } : r;
|
|
154
|
+
} catch {
|
|
155
|
+
return { ok: false, reason: "pairing message could not be opened" };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Six digits from two public keys, independent of their order. */
|
|
160
|
+
export function comparisonCode(a, b) {
|
|
161
|
+
const [x, y] = [String(a), String(b)].sort();
|
|
162
|
+
const n = sha256(Buffer.from(`${INFO}compare\n${x}\n${y}`)).readUInt32BE(0) % 1_000_000;
|
|
163
|
+
return String(n).padStart(6, "0");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const sameBytes = (a, b) => a.length === b.length && timingSafeEqual(a, b);
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Check a WebAuthn assertion against the paired passkey.
|
|
170
|
+
*
|
|
171
|
+
* @param {object} p
|
|
172
|
+
* @param {{authenticatorData: string, clientDataJSON: string, signature: string}} p.assertion base64url fields
|
|
173
|
+
* @param {string} p.credential base64url SPKI of the passkey public key
|
|
174
|
+
* @param {string} p.rpId
|
|
175
|
+
* @param {string} p.origin
|
|
176
|
+
* @param {Buffer} p.challenge the bytes this request binds (SHA-256 of its canonical signed text)
|
|
177
|
+
* @param {number} [p.prevSignCount]
|
|
178
|
+
* @returns {{ok: true, signCount: number} | {ok: false, reason: string}}
|
|
179
|
+
*/
|
|
180
|
+
export function verifyPasskey({
|
|
181
|
+
assertion,
|
|
182
|
+
credential,
|
|
183
|
+
rpId,
|
|
184
|
+
origin,
|
|
185
|
+
challenge,
|
|
186
|
+
prevSignCount = 0,
|
|
187
|
+
}) {
|
|
188
|
+
const authData = fromB64u(assertion?.authenticatorData);
|
|
189
|
+
const clientRaw = fromB64u(assertion?.clientDataJSON);
|
|
190
|
+
if (authData.length < 37) return { ok: false, reason: "authenticator data too short" };
|
|
191
|
+
let client;
|
|
192
|
+
try {
|
|
193
|
+
client = JSON.parse(clientRaw.toString("utf8"));
|
|
194
|
+
} catch {
|
|
195
|
+
return { ok: false, reason: "client data is not JSON" };
|
|
196
|
+
}
|
|
197
|
+
if (client?.type !== "webauthn.get")
|
|
198
|
+
return { ok: false, reason: "client data type is not webauthn.get" };
|
|
199
|
+
if (!sameBytes(fromB64u(client.challenge), Buffer.from(challenge))) {
|
|
200
|
+
return { ok: false, reason: "challenge is not this request" };
|
|
201
|
+
}
|
|
202
|
+
if (client.origin !== origin) return { ok: false, reason: "origin is not the paired one" };
|
|
203
|
+
if (!sameBytes(authData.subarray(0, 32), sha256(Buffer.from(rpId)))) {
|
|
204
|
+
return { ok: false, reason: "rp id is not the paired one" };
|
|
205
|
+
}
|
|
206
|
+
const flags = authData[32];
|
|
207
|
+
if (!(flags & 0x01) || !(flags & 0x04))
|
|
208
|
+
return { ok: false, reason: "the user was not present and verified" };
|
|
209
|
+
const signCount = authData.readUInt32BE(33);
|
|
210
|
+
if ((signCount !== 0 || prevSignCount !== 0) && signCount <= prevSignCount) {
|
|
211
|
+
return { ok: false, reason: "sign count did not grow (a cloned authenticator or a replay)" };
|
|
212
|
+
}
|
|
213
|
+
let good;
|
|
214
|
+
try {
|
|
215
|
+
const key = createPublicKey({ key: fromB64u(credential), format: "der", type: "spki" });
|
|
216
|
+
good = verify(
|
|
217
|
+
"sha256",
|
|
218
|
+
Buffer.concat([authData, sha256(clientRaw)]),
|
|
219
|
+
key,
|
|
220
|
+
fromB64u(assertion.signature),
|
|
221
|
+
);
|
|
222
|
+
} catch {
|
|
223
|
+
good = false;
|
|
224
|
+
}
|
|
225
|
+
return good
|
|
226
|
+
? { ok: true, signCount }
|
|
227
|
+
: { ok: false, reason: "signature does not verify with the paired passkey" };
|
|
228
|
+
}
|