@mmerterden/multi-agent-pipeline 20.34.0 → 20.36.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 +69 -1
- package/README.md +7 -5
- package/README.tr.md +6 -5
- package/docs/architecture.md +3 -3
- package/docs/ecosystem.md +5 -5
- package/docs/facts.json +4 -4
- package/install/catalog-history.json +1 -1
- package/install/claude.mjs +16 -1
- package/install/templates/claude-hooks.json +23 -1
- package/manifest.json +338 -305
- package/package.json +5 -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-pair/SKILL.md +66 -0
- package/pipeline/commands/multi-agent/office-status/SKILL.md +3 -2
- package/pipeline/commands/multi-agent/sync/SKILL.md +3 -3
- 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 +386 -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/cross-cli-contract.md +4 -4
- 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/help/en.md +1 -0
- package/pipeline/multi-agent-refs/help/tr.md +1 -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 +438 -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 +12 -8
- package/pipeline/skills/.skills-index.json +15 -4
- package/pipeline/skills/shared/README.md +4 -3
- package/pipeline/skills/shared/core/apple-archive-compliance/SKILL.md +1 -1
- package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +6 -6
- 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-pair/SKILL.md +19 -0
- package/pipeline/skills/shared/core/multi-agent-office-status/SKILL.md +2 -2
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +3 -3
- 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/pipeline/skills/skills-index.md +2 -1
|
@@ -32,6 +32,12 @@
|
|
|
32
32
|
//
|
|
33
33
|
// Usage:
|
|
34
34
|
// anonymize-findings.mjs [--file <reviewers.json>] [--map <out.json>] [--seed <text>]
|
|
35
|
+
// anonymize-findings.mjs --restore --map <map.json> --in-place <triage.json>
|
|
36
|
+
//
|
|
37
|
+
// --restore runs after the triage verdict is validated: triage carries each
|
|
38
|
+
// finding's `foundBy` label through and never sees which model it stands for,
|
|
39
|
+
// and this writes the reviewer family back from the map for the per-reviewer
|
|
40
|
+
// telemetry that reads `reviewer`.
|
|
35
41
|
//
|
|
36
42
|
// Exit codes: 0 ok, 64 usage, 65 input is not JSON.
|
|
37
43
|
|
|
@@ -161,7 +167,100 @@ export function anonymize(input, { seed } = {}) {
|
|
|
161
167
|
return { findings: tagged, map: { seed: seedText, labelToModel, malformed } };
|
|
162
168
|
}
|
|
163
169
|
|
|
170
|
+
const FAMILIES = ["fable", "opus", "sonnet", "gpt"];
|
|
171
|
+
|
|
172
|
+
/** The reviewer family a map entry names, or null for a model outside the four. */
|
|
173
|
+
export function familyOf(model) {
|
|
174
|
+
const m = String(model ?? "").toLowerCase();
|
|
175
|
+
return FAMILIES.find((f) => m.includes(f)) ?? null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* The triage document with `reviewer` set on every finding whose `foundBy`
|
|
180
|
+
* label the map knows. Findings with no label (deterministic gate findings)
|
|
181
|
+
* or a label the map does not name are left as they are.
|
|
182
|
+
*/
|
|
183
|
+
export function restore(triage, map) {
|
|
184
|
+
const labels = map && typeof map.labelToModel === "object" ? map.labelToModel : {};
|
|
185
|
+
const fill = (f) => {
|
|
186
|
+
if (!f || typeof f !== "object" || typeof f.foundBy !== "string") return f;
|
|
187
|
+
if (!Object.hasOwn(labels, f.foundBy)) return f;
|
|
188
|
+
const family = familyOf(labels[f.foundBy]);
|
|
189
|
+
return family ? { ...f, reviewer: family } : f;
|
|
190
|
+
};
|
|
191
|
+
const out = { ...triage };
|
|
192
|
+
if (Array.isArray(out.accepted)) out.accepted = out.accepted.map(fill);
|
|
193
|
+
for (const key of ["deferred", "rejected"]) {
|
|
194
|
+
if (Array.isArray(out[key]))
|
|
195
|
+
out[key] = out[key].map((e) =>
|
|
196
|
+
e && typeof e === "object" ? { ...e, finding: fill(e.finding) } : e,
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
return out;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* The label map: the --map file when it reads, else the newest
|
|
204
|
+
* reviewIterations[].anonymizationMap in the run state, which outlives /tmp.
|
|
205
|
+
*/
|
|
206
|
+
export function loadMap(mapPath, statePath) {
|
|
207
|
+
const read = (p) => {
|
|
208
|
+
try {
|
|
209
|
+
return p ? JSON.parse(readFileSync(p, "utf8")) : null;
|
|
210
|
+
} catch {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
const fromFile = read(mapPath);
|
|
215
|
+
if (fromFile && typeof fromFile.labelToModel === "object") return fromFile;
|
|
216
|
+
const state = read(statePath);
|
|
217
|
+
const its = Array.isArray(state?.reviewIterations) ? state.reviewIterations : [];
|
|
218
|
+
for (let i = its.length - 1; i >= 0; i--) {
|
|
219
|
+
const m = its[i]?.anonymizationMap;
|
|
220
|
+
if (m && typeof m.labelToModel === "object") return m;
|
|
221
|
+
}
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
|
|
164
225
|
const isMain = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
226
|
+
if (isMain && process.argv.includes("--restore")) {
|
|
227
|
+
const args = process.argv.slice(2);
|
|
228
|
+
const at = (name) => {
|
|
229
|
+
const i = args.indexOf(name);
|
|
230
|
+
return i === -1 ? undefined : args[i + 1];
|
|
231
|
+
};
|
|
232
|
+
const mapPath = at("--map");
|
|
233
|
+
const statePath = at("--state");
|
|
234
|
+
const target = at("--in-place");
|
|
235
|
+
const usage =
|
|
236
|
+
"usage: anonymize-findings.mjs --restore [--map <map.json>] [--state <state.json>] --in-place <triage.json>\n";
|
|
237
|
+
if (
|
|
238
|
+
!target ||
|
|
239
|
+
(!mapPath && !statePath) ||
|
|
240
|
+
args.length !== 3 + 2 * [mapPath, statePath].filter(Boolean).length
|
|
241
|
+
) {
|
|
242
|
+
process.stderr.write(usage);
|
|
243
|
+
process.exit(64);
|
|
244
|
+
}
|
|
245
|
+
let triage;
|
|
246
|
+
try {
|
|
247
|
+
triage = JSON.parse(readFileSync(target, "utf8"));
|
|
248
|
+
} catch (e) {
|
|
249
|
+
process.stderr.write(`input is not JSON: ${e.message}\n`);
|
|
250
|
+
process.exit(65);
|
|
251
|
+
}
|
|
252
|
+
const map = loadMap(mapPath, statePath);
|
|
253
|
+
if (!map) {
|
|
254
|
+
// The verdict stands without the families; only per-reviewer telemetry
|
|
255
|
+
// loses them, so the triage chain is not stopped over it.
|
|
256
|
+
process.stderr.write(
|
|
257
|
+
"warning: no label map (neither --map nor the run state's anonymizationMap); reviewer families not restored\n",
|
|
258
|
+
);
|
|
259
|
+
process.exit(0);
|
|
260
|
+
}
|
|
261
|
+
writeFileSync(target, `${JSON.stringify(restore(triage, map), null, 2)}\n`);
|
|
262
|
+
process.exit(0);
|
|
263
|
+
}
|
|
165
264
|
if (isMain) {
|
|
166
265
|
const args = process.argv.slice(2);
|
|
167
266
|
const flag = (name) => {
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @file answer-check.mjs - PostToolUse hook (AskUserQuestion): an empty
|
|
4
|
+
* answer is not consent.
|
|
5
|
+
*
|
|
6
|
+
* rules.md ("Interaction") says a question submitted without a choice must be
|
|
7
|
+
* asked again or treated as unselected, never resolved to the option marked
|
|
8
|
+
* Recommended. The host returns such a submission as a normal tool result, so
|
|
9
|
+
* the rule depends on the model noticing. This hook names the unanswered
|
|
10
|
+
* questions back to the model as additionalContext whenever it happens.
|
|
11
|
+
*
|
|
12
|
+
* Where the answers come from: the structured result's `answers` map keyed by
|
|
13
|
+
* question text; when only the text form is present, the `"question"="answer"`
|
|
14
|
+
* pairs in it. A result it cannot read either way is left alone.
|
|
15
|
+
*
|
|
16
|
+
* Applies in every session, run or not: it only adds context. Exits 0 on every
|
|
17
|
+
* path and blocks nothing.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { readFileSync } from "node:fs";
|
|
21
|
+
|
|
22
|
+
import { invokedDirectly } from "../lib/invoked-directly.mjs";
|
|
23
|
+
|
|
24
|
+
const MAX_INPUT = 256 * 1024;
|
|
25
|
+
const MAX_LABEL = 60;
|
|
26
|
+
|
|
27
|
+
const label = (q, i) => {
|
|
28
|
+
const raw = String(q?.header || q?.question || `question ${i + 1}`)
|
|
29
|
+
.replace(/\s+/g, " ")
|
|
30
|
+
.trim();
|
|
31
|
+
return raw.length > MAX_LABEL ? `${raw.slice(0, MAX_LABEL - 3)}...` : raw;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** The question text -> answer map from a structured or text result, or null. */
|
|
35
|
+
export function answersOf(response) {
|
|
36
|
+
if (response && typeof response === "object" && !Array.isArray(response)) {
|
|
37
|
+
const a = response.answers;
|
|
38
|
+
if (a && typeof a === "object" && !Array.isArray(a)) return a;
|
|
39
|
+
}
|
|
40
|
+
const text =
|
|
41
|
+
typeof response === "string"
|
|
42
|
+
? response
|
|
43
|
+
: Array.isArray(response)
|
|
44
|
+
? response.map((b) => (typeof b === "string" ? b : (b?.text ?? ""))).join("\n")
|
|
45
|
+
: null;
|
|
46
|
+
if (!text || !/answered/i.test(text)) return null;
|
|
47
|
+
const out = {};
|
|
48
|
+
for (const m of text.matchAll(/"((?:[^"\\]|\\.)*)"="((?:[^"\\]|\\.)*)"/g)) out[m[1]] = m[2];
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Labels of the questions with no answer, or null when the result is unreadable. */
|
|
53
|
+
export function unanswered(payload) {
|
|
54
|
+
if (payload?.tool_name !== "AskUserQuestion") return null;
|
|
55
|
+
const questions = Array.isArray(payload.tool_input?.questions)
|
|
56
|
+
? payload.tool_input.questions
|
|
57
|
+
: [];
|
|
58
|
+
if (!questions.length) return null;
|
|
59
|
+
const answers = answersOf(payload.tool_response);
|
|
60
|
+
if (!answers) return null;
|
|
61
|
+
const keys = Object.keys(answers);
|
|
62
|
+
return questions
|
|
63
|
+
.map((q, i) => {
|
|
64
|
+
const text = String(q?.question ?? "");
|
|
65
|
+
const hit = Object.hasOwn(answers, text)
|
|
66
|
+
? answers[text]
|
|
67
|
+
: keys.length === 1 && questions.length === 1
|
|
68
|
+
? answers[keys[0]]
|
|
69
|
+
: undefined;
|
|
70
|
+
const value = Array.isArray(hit) ? hit.join("") : hit;
|
|
71
|
+
return typeof value === "string" && value.trim() ? null : label(q, i);
|
|
72
|
+
})
|
|
73
|
+
.filter(Boolean);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The hook output for one payload, or null when every question was answered. */
|
|
77
|
+
export function answer(payload) {
|
|
78
|
+
const missing = unanswered(payload);
|
|
79
|
+
if (!missing || !missing.length) return null;
|
|
80
|
+
return {
|
|
81
|
+
hookSpecificOutput: {
|
|
82
|
+
hookEventName: "PostToolUse",
|
|
83
|
+
additionalContext:
|
|
84
|
+
`The user submitted AskUserQuestion without choosing for: ${missing.map((m) => `"${m}"`).join(", ")}. ` +
|
|
85
|
+
"An empty answer is not consent: do not apply the option marked Recommended. " +
|
|
86
|
+
"Ask that question again, or treat the item as unselected and say so.",
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function main() {
|
|
92
|
+
let raw;
|
|
93
|
+
try {
|
|
94
|
+
raw = readFileSync(0, "utf8");
|
|
95
|
+
} catch {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (!raw || raw.length > MAX_INPUT) return;
|
|
99
|
+
let payload;
|
|
100
|
+
try {
|
|
101
|
+
payload = JSON.parse(raw);
|
|
102
|
+
} catch {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const out = answer(payload);
|
|
106
|
+
if (out) process.stdout.write(JSON.stringify(out) + "\n");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
if (invokedDirectly(import.meta.url)) main();
|
|
111
|
+
} catch {
|
|
112
|
+
/* silent by contract */
|
|
113
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# answer-check.sh - PostToolUse hook (AskUserQuestion) for the pipeline.
|
|
3
|
+
#
|
|
4
|
+
# Tells the model which questions came back with no choice, so an empty answer
|
|
5
|
+
# is asked again instead of resolved to the Recommended option. The logic is in
|
|
6
|
+
# answer-check.mjs; this wrapper exists because every hook the pipeline
|
|
7
|
+
# registers is a `bash "$HOME/.<host>/scripts/<name>"` command.
|
|
8
|
+
#
|
|
9
|
+
# Exits 0 on every path: a missing node or any internal error means no
|
|
10
|
+
# context, never a blocked tool call.
|
|
11
|
+
|
|
12
|
+
set -u
|
|
13
|
+
|
|
14
|
+
HERE="$(cd "$(dirname "$0")" 2>/dev/null && pwd || true)"
|
|
15
|
+
CORE="$HERE/answer-check.mjs"
|
|
16
|
+
[ -f "$CORE" ] || exit 0
|
|
17
|
+
command -v node >/dev/null 2>&1 || exit 0
|
|
18
|
+
|
|
19
|
+
node "$CORE" 2>/dev/null || true
|
|
20
|
+
exit 0
|
|
@@ -1509,7 +1509,9 @@ function scriptRunner(results, env) {
|
|
|
1509
1509
|
* publish rights, which an unattended run does not get, so they are
|
|
1510
1510
|
* reported as skipped.
|
|
1511
1511
|
* reportIssueUpdates: update-issue-progress.sh for a GitHub issue the run
|
|
1512
|
-
* recorded,
|
|
1512
|
+
* recorded, with a PR-link comment it posts before the Progress flags
|
|
1513
|
+
* (comment and flags go together), and a PR-link Jira comment when no
|
|
1514
|
+
* channel comment went out.
|
|
1513
1515
|
*
|
|
1514
1516
|
* Every text passes the outbound gate here before a script sees it, and every
|
|
1515
1517
|
* script runs with MULTI_AGENT_UNATTENDED unset (each script's own gate runs
|
|
@@ -1569,7 +1571,12 @@ export function reportAfterPublish({
|
|
|
1569
1571
|
}
|
|
1570
1572
|
if (config?.reportIssueUpdates === true) {
|
|
1571
1573
|
if (state?.githubIssue?.number && state?.taskId) {
|
|
1572
|
-
runScript(
|
|
1574
|
+
runScript(
|
|
1575
|
+
"issue-progress",
|
|
1576
|
+
progressScript,
|
|
1577
|
+
[String(state.taskId)],
|
|
1578
|
+
`Draft PR opened: ${prUrl}\n`,
|
|
1579
|
+
);
|
|
1573
1580
|
}
|
|
1574
1581
|
if (jiraKey && !jiraPosted) {
|
|
1575
1582
|
runScript("jira-pr-link", jiraScript, ["--issue", jiraKey], `Draft PR opened: ${prUrl}\n`);
|
|
@@ -305,7 +305,7 @@ import { runMain } from "../lib/fatal.mjs";
|
|
|
305
305
|
import { invokedDirectly } from "../lib/invoked-directly.mjs";
|
|
306
306
|
|
|
307
307
|
/** The contract as a whole; pipeline/contract/manifest.json carries the same. */
|
|
308
|
-
export const CONTRACT_VERSION = "1.
|
|
308
|
+
export const CONTRACT_VERSION = "1.7.0";
|
|
309
309
|
/** The majors `?v=` accepts. A new major keeps the previous one here for one minor. */
|
|
310
310
|
export const SUPPORTED_MAJORS = [1];
|
|
311
311
|
export const BIND_HOST = "127.0.0.1";
|
|
@@ -1528,6 +1528,33 @@ function evidenceRoute({ params }) {
|
|
|
1528
1528
|
}
|
|
1529
1529
|
}
|
|
1530
1530
|
|
|
1531
|
+
/**
|
|
1532
|
+
* GET /v1/prs/reach: work-items.mjs reach as a child process, because loading
|
|
1533
|
+
* a repository's graph is a synchronous parse of a file that can run to tens
|
|
1534
|
+
* of megabytes, and no other route may wait behind it.
|
|
1535
|
+
*/
|
|
1536
|
+
async function prReachRoute() {
|
|
1537
|
+
const r = await spawnAsync(process.execPath, [join(HERE, "work-items.mjs"), "reach"], {
|
|
1538
|
+
env: process.env,
|
|
1539
|
+
timeoutMs: 180_000,
|
|
1540
|
+
maxBytes: 2 * 1024 * 1024,
|
|
1541
|
+
});
|
|
1542
|
+
let doc;
|
|
1543
|
+
try {
|
|
1544
|
+
doc = JSON.parse(r.stdout);
|
|
1545
|
+
} catch {
|
|
1546
|
+
doc = null;
|
|
1547
|
+
}
|
|
1548
|
+
if (r.status !== 0 || !doc || !Array.isArray(doc.items)) {
|
|
1549
|
+
throw new HttpError(
|
|
1550
|
+
502,
|
|
1551
|
+
"upstream-failed",
|
|
1552
|
+
"the reach of the pull requests could not be measured",
|
|
1553
|
+
);
|
|
1554
|
+
}
|
|
1555
|
+
return doc;
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1531
1558
|
function workError(err) {
|
|
1532
1559
|
if (!(err instanceof WorkError)) return err;
|
|
1533
1560
|
if (err.code === "no-run" || err.code === "bad-key")
|
|
@@ -1899,6 +1926,13 @@ export const ROUTES = [
|
|
|
1899
1926
|
({ redact }) => pullRequests({ redact }),
|
|
1900
1927
|
),
|
|
1901
1928
|
},
|
|
1929
|
+
{
|
|
1930
|
+
method: "GET",
|
|
1931
|
+
path: "/v1/prs/reach",
|
|
1932
|
+
query: [],
|
|
1933
|
+
remote: "local-only",
|
|
1934
|
+
handle: shared(() => "prs-reach", prReachRoute),
|
|
1935
|
+
},
|
|
1902
1936
|
{ method: "GET", path: "/v1/tools", query: [], handle: toolsRoute },
|
|
1903
1937
|
{
|
|
1904
1938
|
method: "POST",
|
|
@@ -54,14 +54,18 @@
|
|
|
54
54
|
|
|
55
55
|
import { spawnSync } from "node:child_process";
|
|
56
56
|
import {
|
|
57
|
+
copyFileSync,
|
|
57
58
|
existsSync,
|
|
59
|
+
mkdtempSync,
|
|
58
60
|
readFileSync,
|
|
59
61
|
realpathSync,
|
|
60
62
|
renameSync,
|
|
63
|
+
rmSync,
|
|
61
64
|
statSync,
|
|
62
65
|
writeFileSync,
|
|
63
66
|
} from "node:fs";
|
|
64
|
-
import {
|
|
67
|
+
import { tmpdir } from "node:os";
|
|
68
|
+
import { basename, dirname, join } from "node:path";
|
|
65
69
|
|
|
66
70
|
import { decisionEntry, withDecision } from "../lib/decisions.mjs";
|
|
67
71
|
import { invokedDirectly } from "../lib/invoked-directly.mjs";
|
|
@@ -103,31 +107,77 @@ export const ATTENDED_RECORDED = new Set([
|
|
|
103
107
|
"plan-coverage",
|
|
104
108
|
"evidence-gate/build",
|
|
105
109
|
"evidence-gate/test",
|
|
110
|
+
"review-decision",
|
|
106
111
|
]);
|
|
107
112
|
|
|
108
113
|
/** What an attended commit is checked for (pre-commit-check.sh, `evidence`). */
|
|
114
|
+
/**
|
|
115
|
+
* Fix loops with a hard cap (rules.md: three attempts, the fourth failure
|
|
116
|
+
* pauses): consecutive failing verdicts of one of these gates count, and the
|
|
117
|
+
* append that reaches RETRY_CAP pauses the run with a halt reason.
|
|
118
|
+
*/
|
|
119
|
+
export const RETRY_CAPPED = new Set(["evidence-gate/build", "evidence-gate/test"]);
|
|
120
|
+
export const RETRY_CAP = 4;
|
|
121
|
+
|
|
122
|
+
/** Consecutive failing verdicts at the end of `gates` for one gate. */
|
|
123
|
+
export function trailingFailures(gates, gate) {
|
|
124
|
+
let n = 0;
|
|
125
|
+
for (let i = gates.length - 1; i >= 0; i--) {
|
|
126
|
+
const e = gates[i];
|
|
127
|
+
if (!e || e.gate !== gate) continue;
|
|
128
|
+
if (e.verdict !== "fail") break;
|
|
129
|
+
n += 1;
|
|
130
|
+
}
|
|
131
|
+
return n;
|
|
132
|
+
}
|
|
133
|
+
|
|
109
134
|
export const ATTENDED_COMMIT_EVIDENCE = ["evidence-gate/build", "evidence-gate/test"];
|
|
110
135
|
|
|
111
|
-
/**
|
|
112
|
-
export const
|
|
136
|
+
/** Asked for too once the run is in its commit phase: the review verdict. */
|
|
137
|
+
export const ATTENDED_REVIEW_EVIDENCE = "review-decision";
|
|
138
|
+
export const ATTENDED_REVIEW_FROM_PHASE = 4;
|
|
113
139
|
|
|
114
140
|
/**
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
141
|
+
* prefs.global.attendedCommitGate.mode values. `auto`, the default, blocks
|
|
142
|
+
* the commit when the run is bound to the committing session (the session
|
|
143
|
+
* driving it) and warns otherwise.
|
|
144
|
+
*/
|
|
145
|
+
export const ATTENDED_COMMIT_MODES = ["off", "warn", "block", "auto"];
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* The evidence an attended commit on `sha` lacks: every gate in
|
|
149
|
+
* ATTENDED_COMMIT_EVIDENCE, plus the review verdict from the commit phase on,
|
|
150
|
+
* whose latest entry for that sha, attended or not, is missing or not passing.
|
|
151
|
+
* A checkpoint commit during development comes before any review and is held
|
|
152
|
+
* to build and test only.
|
|
118
153
|
*
|
|
119
154
|
* @param {object} state
|
|
120
155
|
* @param {string} sha
|
|
121
156
|
* @returns {string[]}
|
|
122
157
|
*/
|
|
123
|
-
export function attendedEvidenceGaps(state, sha) {
|
|
124
|
-
const
|
|
125
|
-
|
|
158
|
+
export function attendedEvidenceGaps(state, sha, { sameTree } = {}) {
|
|
159
|
+
const wanted = [...ATTENDED_COMMIT_EVIDENCE];
|
|
160
|
+
if (Number(state?.currentPhase) >= ATTENDED_REVIEW_FROM_PHASE)
|
|
161
|
+
wanted.push(ATTENDED_REVIEW_EVIDENCE);
|
|
162
|
+
const asked = (Array.isArray(state?.gates) ? state.gates : []).filter((e) =>
|
|
163
|
+
wanted.includes(e?.gate),
|
|
164
|
+
);
|
|
165
|
+
const latest = sameTree
|
|
166
|
+
? latestByGateWhere(asked, (s) => s === sha || sameTree(s))
|
|
167
|
+
: latestByGate(asked, sha);
|
|
168
|
+
return wanted.filter((g) => {
|
|
126
169
|
const e = latest.get(g);
|
|
127
170
|
return !e || !PASSING.has(e.verdict);
|
|
128
171
|
});
|
|
129
172
|
}
|
|
130
173
|
|
|
174
|
+
/** The mode that applies: an explicit off/warn/block, else block for a bound run. */
|
|
175
|
+
export function effectiveCommitMode(pref, bound) {
|
|
176
|
+
const mode = ATTENDED_COMMIT_MODES.includes(pref) ? pref : "auto";
|
|
177
|
+
if (mode !== "auto") return mode;
|
|
178
|
+
return bound ? "block" : "warn";
|
|
179
|
+
}
|
|
180
|
+
|
|
131
181
|
const QUESTION_ID = /^[a-z][a-z0-9-]*$/;
|
|
132
182
|
const OPTION_ID = /^[a-z0-9][a-z0-9+_-]*$/;
|
|
133
183
|
const QUESTION_KEYS = ["id", "stepId", "text", "kind", "options"];
|
|
@@ -184,6 +234,54 @@ export class LedgerError extends Error {
|
|
|
184
234
|
* HEAD of the repository at `dir`, or null outside one.
|
|
185
235
|
* @param {string} dir
|
|
186
236
|
*/
|
|
237
|
+
/**
|
|
238
|
+
* A predicate: does a recorded sha hold the content this commit records?
|
|
239
|
+
* Yes when its tree is the index's, or the tree `git add -A` would stage from
|
|
240
|
+
* the working tree (built in a throwaway copy of the index, so the real index
|
|
241
|
+
* is untouched). The hook runs before a `git add ... && git commit` line
|
|
242
|
+
* stages anything, and Phase 4's `git reset HEAD~1` leaves a checkpoint
|
|
243
|
+
* commit's new files untracked; both still match the evidence they were
|
|
244
|
+
* tested with, while a file added after the evidence does not. Each sha is
|
|
245
|
+
* asked once; anything git cannot answer is "no".
|
|
246
|
+
*/
|
|
247
|
+
export function sameTreeAs(dir) {
|
|
248
|
+
const run = (args, env) => {
|
|
249
|
+
const r = spawnSync("git", ["-C", dir, ...args], {
|
|
250
|
+
encoding: "utf8",
|
|
251
|
+
timeout: 20000,
|
|
252
|
+
env: env ? { ...process.env, ...env } : process.env,
|
|
253
|
+
});
|
|
254
|
+
return r.status === 0 ? r.stdout.trim() : null;
|
|
255
|
+
};
|
|
256
|
+
const indexTree = run(["write-tree"]);
|
|
257
|
+
let workTree = null;
|
|
258
|
+
const indexPath = run(["rev-parse", "--path-format=absolute", "--git-path", "index"]);
|
|
259
|
+
if (indexPath) {
|
|
260
|
+
const scratch = mkdtempSync(join(tmpdir(), "ma-tree-"));
|
|
261
|
+
const copy = join(scratch, "index");
|
|
262
|
+
try {
|
|
263
|
+
if (existsSync(indexPath)) copyFileSync(indexPath, copy);
|
|
264
|
+
if (run(["add", "-A"], { GIT_INDEX_FILE: copy }) !== null) {
|
|
265
|
+
workTree = run(["write-tree"], { GIT_INDEX_FILE: copy });
|
|
266
|
+
}
|
|
267
|
+
} catch {
|
|
268
|
+
workTree = null;
|
|
269
|
+
} finally {
|
|
270
|
+
rmSync(scratch, { recursive: true, force: true });
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
const targets = new Set([indexTree, workTree].filter(Boolean));
|
|
274
|
+
const seen = new Map();
|
|
275
|
+
return (sha) => {
|
|
276
|
+
if (!targets.size || typeof sha !== "string" || !/^[0-9a-f]{7,64}$/.test(sha)) return false;
|
|
277
|
+
if (!seen.has(sha)) {
|
|
278
|
+
const tree = run(["rev-parse", "--verify", "--quiet", `${sha}^{tree}`]);
|
|
279
|
+
seen.set(sha, Boolean(tree) && targets.has(tree));
|
|
280
|
+
}
|
|
281
|
+
return seen.get(sha);
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
187
285
|
export function headSha(dir) {
|
|
188
286
|
const r = spawnSync("git", ["-C", dir, "rev-parse", "HEAD"], {
|
|
189
287
|
encoding: "utf8",
|
|
@@ -399,11 +497,22 @@ export async function record(
|
|
|
399
497
|
detail: detail && typeof detail === "object" ? detail : { value: detail },
|
|
400
498
|
...(attended ? { attended: true } : {}),
|
|
401
499
|
};
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
gates
|
|
405
|
-
|
|
406
|
-
|
|
500
|
+
let capped = false;
|
|
501
|
+
await updateState(path, (s) => {
|
|
502
|
+
const gates = [...(Array.isArray(s.gates) ? s.gates : []), entry];
|
|
503
|
+
const next = { ...s, gates };
|
|
504
|
+
if (
|
|
505
|
+
RETRY_CAPPED.has(gate) &&
|
|
506
|
+
verdict === "fail" &&
|
|
507
|
+
trailingFailures(gates, gate) >= RETRY_CAP
|
|
508
|
+
) {
|
|
509
|
+
capped = true;
|
|
510
|
+
next.status = "paused";
|
|
511
|
+
next.haltReason = `${phaseOf(s)}:retry-cap: ${gate} failed ${RETRY_CAP} times in a row; the fix loop stops here for the user`;
|
|
512
|
+
}
|
|
513
|
+
return next;
|
|
514
|
+
});
|
|
515
|
+
return { written: true, statePath: path, entry, capped };
|
|
407
516
|
}
|
|
408
517
|
|
|
409
518
|
/**
|
|
@@ -494,9 +603,14 @@ function phaseOf(state) {
|
|
|
494
603
|
* Latest entry per gate for one sha. "Latest" is by `at`, then by position.
|
|
495
604
|
*/
|
|
496
605
|
export function latestByGate(gates, sha) {
|
|
606
|
+
return latestByGateWhere(gates, (s) => s === sha);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/** latestByGate over the entries whose sha `accept` takes. */
|
|
610
|
+
export function latestByGateWhere(gates, accept) {
|
|
497
611
|
const out = new Map();
|
|
498
612
|
(Array.isArray(gates) ? gates : []).forEach((e, i) => {
|
|
499
|
-
if (!e || typeof e !== "object" ||
|
|
613
|
+
if (!e || typeof e !== "object" || typeof e.gate !== "string" || !accept(e.sha)) return;
|
|
500
614
|
const prev = out.get(e.gate);
|
|
501
615
|
const t = Date.parse(e.at) || 0;
|
|
502
616
|
if (!prev || t > prev.t || (t === prev.t && i > prev.i)) out.set(e.gate, { e, t, i });
|
|
@@ -570,6 +684,12 @@ async function main(argv) {
|
|
|
570
684
|
// Gates off: nothing is written and nothing is said.
|
|
571
685
|
if (!r.written && r.reason === attendedVerdict(opts.gate).reason) return 0;
|
|
572
686
|
if (!r.written) process.stderr.write(`gate-ledger: nothing written: ${r.reason}\n`);
|
|
687
|
+
if (r.capped) {
|
|
688
|
+
process.stderr.write(
|
|
689
|
+
`gate-ledger: ${opts.gate} failed ${RETRY_CAP} times in a row; the run is paused (retry cap). Report what was tried and ask the user how to continue.\n`,
|
|
690
|
+
);
|
|
691
|
+
return 5;
|
|
692
|
+
}
|
|
573
693
|
return r.written ? 0 : 3;
|
|
574
694
|
}
|
|
575
695
|
if (mode === "check") {
|
|
@@ -619,9 +739,8 @@ async function main(argv) {
|
|
|
619
739
|
// mode that commits nothing, evidence present), 1 warn, 4 block. Never 2
|
|
620
740
|
// or 3 for a missing run: outside the pipeline a commit is none of this
|
|
621
741
|
// hook's business.
|
|
622
|
-
const pref = globalPref("attendedCommitGate.mode", "
|
|
623
|
-
|
|
624
|
-
if (gateMode === "off") return 0;
|
|
742
|
+
const pref = globalPref("attendedCommitGate.mode", "auto");
|
|
743
|
+
if (pref === "off") return 0;
|
|
625
744
|
let session = "";
|
|
626
745
|
if (opts["hook-input"] === "-") {
|
|
627
746
|
try {
|
|
@@ -638,7 +757,11 @@ async function main(argv) {
|
|
|
638
757
|
if (modeEntry(state)?.commits === false) return 0;
|
|
639
758
|
const sha = opts.sha || headSha(repo);
|
|
640
759
|
if (!sha) return 0;
|
|
641
|
-
const
|
|
760
|
+
const launched = process.env.MULTI_AGENT_SESSION_ID || "";
|
|
761
|
+
const bound =
|
|
762
|
+
(session && state.hostSessionId === session) || (launched && state.sessionId === launched);
|
|
763
|
+
const gateMode = effectiveCommitMode(pref, Boolean(bound));
|
|
764
|
+
const missing = attendedEvidenceGaps(state, sha, { sameTree: sameTreeAs(repo) });
|
|
642
765
|
if (opts.json)
|
|
643
766
|
writeFileSync(1, `${JSON.stringify({ statePath: path, sha, gateMode, missing })}\n`);
|
|
644
767
|
if (!missing.length) return 0;
|
|
@@ -647,9 +770,9 @@ async function main(argv) {
|
|
|
647
770
|
2,
|
|
648
771
|
`${[
|
|
649
772
|
`pre-commit-check: ${verb}: the run's gate ledger (${path}) has no passing ${missing.join(" / ")} for HEAD ${sha.slice(0, 12)}.`,
|
|
650
|
-
" Run the build and the tests through evidence-gate.mjs (Phase 2 Gate 1) so the commit rests on recorded evidence.",
|
|
773
|
+
" Run the build and the tests through evidence-gate.mjs (Phase 2 Gate 1), and in the commit phase the review verdict (review-decision-gate.mjs), so the commit rests on recorded evidence.",
|
|
651
774
|
gateMode === "block"
|
|
652
|
-
? " prefs.global.attendedCommitGate.mode
|
|
775
|
+
? " This session drives the run, so the commit waits for the evidence; prefs.global.attendedCommitGate.mode warn lets it through."
|
|
653
776
|
: " The commit goes ahead; prefs.global.attendedCommitGate.mode block refuses it instead.",
|
|
654
777
|
].join("\n")}\n`,
|
|
655
778
|
);
|