@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
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @file test-stability-gate.mjs - the repeated runs of the new and changed
|
|
4
|
+
* tests agree, or the result is not GREEN.
|
|
5
|
+
*
|
|
6
|
+
* Phase 2's stability rule runs each test added or changed in the diff
|
|
7
|
+
* `testStability.repeatCount` times and keeps every run as
|
|
8
|
+
* `.pipeline/repeat-<k>.junit.xml`. This gate reads those files and compares
|
|
9
|
+
* each test case's outcome across the runs: a case that passed in some runs
|
|
10
|
+
* and failed in others is a flake, however the last run ended.
|
|
11
|
+
*
|
|
12
|
+
* A case is keyed by classname and name. Outcomes are pass, fail (a failure
|
|
13
|
+
* or error element) and skip; a case missing from a run counts as not run.
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* test-stability-gate.mjs --dir <dir> --expect <N>
|
|
17
|
+
*
|
|
18
|
+
* Output (stdout, JSON): {ok, expected, runs, missingRuns[], cases, flaky[], failedEverywhere[]}
|
|
19
|
+
* flaky [{test, passed, failed, of}] outcomes disagree across runs
|
|
20
|
+
* failedEverywhere tests that failed in every run (stable, but not GREEN)
|
|
21
|
+
*
|
|
22
|
+
* Exit: 0 every case passed in every run; 1 a flake, a run missing, a test
|
|
23
|
+
* that failed, or no test cases at all; 64 usage.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
27
|
+
import { join } from "node:path";
|
|
28
|
+
|
|
29
|
+
import { invokedDirectly } from "../lib/invoked-directly.mjs";
|
|
30
|
+
|
|
31
|
+
const attr = (tag, name) => {
|
|
32
|
+
const m = new RegExp(`\\b${name}="([^"]*)"`).exec(tag);
|
|
33
|
+
return m ? m[1] : "";
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** Test case key -> outcome for one JUnit XML document. */
|
|
37
|
+
export function outcomes(xml) {
|
|
38
|
+
const out = new Map();
|
|
39
|
+
for (const m of String(xml).matchAll(/<testcase\b([^>]*?)(?:\/>|>([\s\S]*?)<\/testcase>)/g)) {
|
|
40
|
+
const key = `${attr(m[1], "classname")}::${attr(m[1], "name")}`;
|
|
41
|
+
const body = m[2] ?? "";
|
|
42
|
+
const outcome = /<(?:failure|error)\b/.test(body)
|
|
43
|
+
? "fail"
|
|
44
|
+
: /<skipped\b/.test(body)
|
|
45
|
+
? "skip"
|
|
46
|
+
: "pass";
|
|
47
|
+
const prev = out.get(key);
|
|
48
|
+
out.set(key, prev === "fail" || outcome === "fail" ? "fail" : outcome);
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Compare the per-run outcome maps. */
|
|
54
|
+
export function compare(runs) {
|
|
55
|
+
const keys = new Set();
|
|
56
|
+
for (const r of runs) for (const k of r.keys()) keys.add(k);
|
|
57
|
+
const flaky = [];
|
|
58
|
+
const failedEverywhere = [];
|
|
59
|
+
for (const k of [...keys].sort()) {
|
|
60
|
+
const seen = runs.map((r) => r.get(k) ?? "missing");
|
|
61
|
+
const passed = seen.filter((o) => o === "pass").length;
|
|
62
|
+
const failed = seen.filter((o) => o === "fail" || o === "missing").length;
|
|
63
|
+
if (seen.every((o) => o === "skip")) continue;
|
|
64
|
+
if (passed && failed) flaky.push({ test: k, passed, failed, of: runs.length });
|
|
65
|
+
else if (!passed && failed) failedEverywhere.push(k);
|
|
66
|
+
}
|
|
67
|
+
return { cases: keys.size, flaky, failedEverywhere };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function evaluate(dir, expected) {
|
|
71
|
+
const runs = [];
|
|
72
|
+
const missingRuns = [];
|
|
73
|
+
for (let k = 1; k <= expected; k++) {
|
|
74
|
+
const p = join(dir, `repeat-${k}.junit.xml`);
|
|
75
|
+
if (!existsSync(p)) {
|
|
76
|
+
missingRuns.push(k);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
runs.push(outcomes(readFileSync(p, "utf8")));
|
|
80
|
+
}
|
|
81
|
+
const c = compare(runs);
|
|
82
|
+
return {
|
|
83
|
+
ok:
|
|
84
|
+
missingRuns.length === 0 &&
|
|
85
|
+
c.cases > 0 &&
|
|
86
|
+
c.flaky.length === 0 &&
|
|
87
|
+
c.failedEverywhere.length === 0,
|
|
88
|
+
expected,
|
|
89
|
+
runs: runs.length,
|
|
90
|
+
missingRuns,
|
|
91
|
+
...c,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function main() {
|
|
96
|
+
const argv = process.argv.slice(2);
|
|
97
|
+
const get = (f) => {
|
|
98
|
+
const i = argv.indexOf(f);
|
|
99
|
+
return i >= 0 ? argv[i + 1] : undefined;
|
|
100
|
+
};
|
|
101
|
+
const dir = get("--dir");
|
|
102
|
+
const expected = Number(get("--expect"));
|
|
103
|
+
if (argv.length !== 4 || !dir || !Number.isInteger(expected) || expected < 1 || expected > 50) {
|
|
104
|
+
console.error("usage: test-stability-gate.mjs --dir <dir> --expect <N>");
|
|
105
|
+
return 64;
|
|
106
|
+
}
|
|
107
|
+
const out = evaluate(dir, expected);
|
|
108
|
+
process.stdout.write(JSON.stringify(out) + "\n");
|
|
109
|
+
return out.ok ? 0 : 1;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (invokedDirectly(import.meta.url)) process.exit(main());
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
# # updateCheck.ttlHours, else 24)
|
|
31
31
|
# bash pipeline/scripts/update-check.sh --force # ignore cache
|
|
32
32
|
# bash pipeline/scripts/update-check.sh --print-required # emit the floor only
|
|
33
|
+
# bash pipeline/scripts/update-check.sh --cache-only # cached answer at any age, never the registry
|
|
33
34
|
#
|
|
34
35
|
# Cache file: ~/.claude/logs/multi-agent/.update-check ("epoch|latest|required").
|
|
35
36
|
# A legacy two-field cache ("epoch|latest") still reads - the missing third
|
|
@@ -47,6 +48,7 @@ FAIL_TTL_SECONDS=3600
|
|
|
47
48
|
LOCAL_VERSION=""
|
|
48
49
|
FORCE=0
|
|
49
50
|
PRINT_REQUIRED=0
|
|
51
|
+
CACHE_ONLY=0
|
|
50
52
|
|
|
51
53
|
while [ $# -gt 0 ]; do
|
|
52
54
|
case "$1" in
|
|
@@ -54,6 +56,7 @@ while [ $# -gt 0 ]; do
|
|
|
54
56
|
--ttl-hours) TTL_HOURS="${2:-}"; shift; [ $# -gt 0 ] && shift ;;
|
|
55
57
|
--force) FORCE=1; shift ;;
|
|
56
58
|
--print-required) PRINT_REQUIRED=1; shift ;;
|
|
59
|
+
--cache-only) CACHE_ONLY=1; shift ;;
|
|
57
60
|
*) shift ;;
|
|
58
61
|
esac
|
|
59
62
|
done
|
|
@@ -124,12 +127,15 @@ if [ "$FORCE" -eq 0 ] && [ -f "$CACHE_FILE" ]; then
|
|
|
124
127
|
case "$cached_epoch" in (*[!0-9]*|"") cached_epoch=0 ;; esac
|
|
125
128
|
# A stamp in the future is a clock or copy mistake, not a fresh answer.
|
|
126
129
|
[ "$cached_epoch" -gt "$now" ] && cached_epoch=0
|
|
127
|
-
if [ $((now - cached_epoch)) -lt $((TTL_HOURS * 3600)) ] && [ -n "$cached_latest" ]; then
|
|
130
|
+
if { [ "$CACHE_ONLY" -eq 1 ] || [ $((now - cached_epoch)) -lt $((TTL_HOURS * 3600)) ]; } && [ -n "$cached_latest" ]; then
|
|
128
131
|
latest="$cached_latest"
|
|
129
132
|
required="$cached_required"
|
|
130
133
|
fi
|
|
131
134
|
fi
|
|
132
135
|
|
|
136
|
+
# A cache-only caller gets no answer rather than a network wait.
|
|
137
|
+
[ -z "$latest" ] && [ "$CACHE_ONLY" -eq 1 ] && exit 0
|
|
138
|
+
|
|
133
139
|
# Stale or missing cache -> one bounded registry call (silent on any failure).
|
|
134
140
|
# The abbreviated packument carries dist-tags at a quarter of the full document.
|
|
135
141
|
# A failed read is remembered for FAIL_TTL_SECONDS in "<cache>.fail", so an
|
|
@@ -11,7 +11,13 @@
|
|
|
11
11
|
# never hand-edited.
|
|
12
12
|
#
|
|
13
13
|
# Usage:
|
|
14
|
-
# update-issue-progress.sh <task-id>
|
|
14
|
+
# update-issue-progress.sh <task-id> [--comment-file <path>]
|
|
15
|
+
#
|
|
16
|
+
# --comment-file posts that comment on the issue first (through the outbound
|
|
17
|
+
# gate, with the `Ref: #N` footer appended when missing), then the flags, so
|
|
18
|
+
# a caller with no separate comment step (the unattended runner) still writes
|
|
19
|
+
# both. `--body-file` is the same flag under the name the runner's script
|
|
20
|
+
# runner passes. Without it the flags wait for a comment already on the issue.
|
|
15
21
|
#
|
|
16
22
|
# Inputs:
|
|
17
23
|
# $HOME/.claude/logs/multi-agent/{project}/{taskId}/agent-state.json
|
|
@@ -26,6 +32,9 @@
|
|
|
26
32
|
# 2 missing agent-state.json
|
|
27
33
|
# 3 missing required flags
|
|
28
34
|
# 4 GitHub edit failed
|
|
35
|
+
# 5 no issue comment from this run yet: the comment is posted first and the
|
|
36
|
+
# flags follow it, both or neither (channels/issue-comment.md)
|
|
37
|
+
# 6 issue coordinates in the state that are not an owner/repo and a number
|
|
29
38
|
|
|
30
39
|
set -euo pipefail
|
|
31
40
|
|
|
@@ -118,9 +127,17 @@ EOF
|
|
|
118
127
|
|
|
119
128
|
TASK_ID="${1:-}"
|
|
120
129
|
if [ -z "$TASK_ID" ]; then
|
|
121
|
-
echo "usage: update-issue-progress.sh <task-id>" >&2
|
|
130
|
+
echo "usage: update-issue-progress.sh <task-id> [--comment-file <path>]" >&2
|
|
122
131
|
exit 64
|
|
123
132
|
fi
|
|
133
|
+
COMMENT_FILE=""
|
|
134
|
+
if [ "$TASK_ID" != "--rewrite" ] && [ "$#" -gt 1 ]; then
|
|
135
|
+
if [ "$#" -ne 3 ] || { [ "$2" != "--comment-file" ] && [ "$2" != "--body-file" ]; } || [ ! -f "$3" ]; then
|
|
136
|
+
echo "usage: update-issue-progress.sh <task-id> [--comment-file <path>]" >&2
|
|
137
|
+
exit 64
|
|
138
|
+
fi
|
|
139
|
+
COMMENT_FILE="$3"
|
|
140
|
+
fi
|
|
124
141
|
|
|
125
142
|
# Internal test seam: `--rewrite <body-file>` runs only the Progress-block
|
|
126
143
|
# rewrite (placeholder glyphs) and prints the result. No state, no gh.
|
|
@@ -155,6 +172,11 @@ if [ -z "$ORG_REPO" ] || [ -z "$ISSUE_NUM" ]; then
|
|
|
155
172
|
echo "update-issue-progress: no githubIssue in state for task=$TASK_ID (Jira-originated task, or issue not yet linked)" >&2
|
|
156
173
|
exit 3
|
|
157
174
|
fi
|
|
175
|
+
if ! printf '%s' "$ISSUE_NUM" | grep -Eq '^[0-9]{1,9}$' ||
|
|
176
|
+
! printf '%s' "$ORG_REPO" | grep -Eq '^[A-Za-z0-9][A-Za-z0-9-]{0,38}/[A-Za-z0-9._-]{1,100}$'; then
|
|
177
|
+
echo "update-issue-progress: githubIssue in state is not an owner/repo and a number; nothing written" >&2
|
|
178
|
+
exit 6
|
|
179
|
+
fi
|
|
158
180
|
|
|
159
181
|
# Read the four flags (default false).
|
|
160
182
|
F_IMPL=$(jq -r '.flags.implementation // false' "$AGENT_STATE")
|
|
@@ -190,17 +212,60 @@ NEW_BLOCK=$(build_progress_block "$G_IMPL" "$G_TEST" "$G_CC" "$G_WIKI")
|
|
|
190
212
|
# comment, next heading, or EOF) - see rewrite_progress_body above.
|
|
191
213
|
rewrite_progress_body "$NEW_BLOCK" "$TMP_BODY" > "$TMP_NEW"
|
|
192
214
|
|
|
193
|
-
#
|
|
194
|
-
|
|
215
|
+
# Whether the body changes (an unchanged body is an idempotent no-op for the
|
|
216
|
+
# flags), and the new body's outbound check before anything is posted, so a
|
|
217
|
+
# refused body never leaves a comment behind without its flags.
|
|
218
|
+
CHANGED=true
|
|
219
|
+
cmp -s "$TMP_BODY" "$TMP_NEW" && CHANGED=false
|
|
220
|
+
if [ "$CHANGED" = true ] && ! ma_outbound_gate "$TMP_NEW"; then
|
|
221
|
+
echo "update-issue-progress: outbound gate refused the new body; #$ISSUE_NUM left unchanged" >&2
|
|
222
|
+
exit 7
|
|
223
|
+
fi
|
|
224
|
+
|
|
225
|
+
# Both or neither: the flags follow this run's issue comment, never stand in
|
|
226
|
+
# for it. With --comment-file the comment is posted here, first, whether or
|
|
227
|
+
# not the flags change. Otherwise it
|
|
228
|
+
# is the one the authenticated account posted on the issue since the run
|
|
229
|
+
# started, carrying the template's `Ref: #N` footer.
|
|
230
|
+
if [ -n "$COMMENT_FILE" ]; then
|
|
231
|
+
TMP_COMMENT=$(mktemp "/tmp/issue-comment-${TASK_ID}.XXXXXX")
|
|
232
|
+
trap 'rm -f "$TMP_BODY" "$TMP_NEW" "$TMP_COMMENT"' EXIT
|
|
233
|
+
cat "$COMMENT_FILE" > "$TMP_COMMENT"
|
|
234
|
+
grep -qF "Ref: #$ISSUE_NUM" "$TMP_COMMENT" || printf '\n\nRef: #%s\n' "$ISSUE_NUM" >> "$TMP_COMMENT"
|
|
235
|
+
if ! ma_outbound_gate "$TMP_COMMENT"; then
|
|
236
|
+
echo "update-issue-progress: outbound gate refused the comment; #$ISSUE_NUM left unchanged" >&2
|
|
237
|
+
exit 7
|
|
238
|
+
fi
|
|
239
|
+
if ! gh issue comment "$ISSUE_NUM" --repo "$ORG_REPO" --body-file "$TMP_COMMENT" >/dev/null; then
|
|
240
|
+
echo "update-issue-progress: gh issue comment failed for #$ISSUE_NUM; flags left unchanged" >&2
|
|
241
|
+
exit 4
|
|
242
|
+
fi
|
|
243
|
+
elif [ "$CHANGED" = true ]; then
|
|
244
|
+
# The comments API reads `since` as an ISO 8601 instant; an offset's `+`
|
|
245
|
+
# would arrive as a space, so the start time goes out in UTC.
|
|
246
|
+
STARTED=$(jq -r '.startedAt // empty' "$AGENT_STATE")
|
|
247
|
+
STARTED=$(node -e 'const d = new Date(process.argv[1]); if (process.argv[1] && !isNaN(d)) process.stdout.write(d.toISOString().replace(/\.\d{3}Z$/, "Z"));' "$STARTED" 2>/dev/null || true)
|
|
248
|
+
ME=$(gh api user --jq .login 2>/dev/null || true)
|
|
249
|
+
if ! printf '%s' "$ME" | grep -Eq '^[A-Za-z0-9][A-Za-z0-9-]{0,38}(\[bot\])?$'; then
|
|
250
|
+
echo "update-issue-progress: could not read the GitHub account; #$ISSUE_NUM left unchanged" >&2
|
|
251
|
+
exit 5
|
|
252
|
+
fi
|
|
253
|
+
COMMENTS_PATH="repos/$ORG_REPO/issues/$ISSUE_NUM/comments"
|
|
254
|
+
[ -n "$STARTED" ] && COMMENTS_PATH="$COMMENTS_PATH?since=$STARTED"
|
|
255
|
+
if ! gh api --paginate "$COMMENTS_PATH" \
|
|
256
|
+
--jq ".[] | select(.user.login == \"$ME\") | select(.body | contains(\"Ref: #$ISSUE_NUM\")) | .id" 2>/dev/null |
|
|
257
|
+
grep -q .; then
|
|
258
|
+
echo "update-issue-progress: no issue comment from this run on #$ISSUE_NUM; post it first (comment and flags go together)" >&2
|
|
259
|
+
exit 5
|
|
260
|
+
fi
|
|
261
|
+
fi
|
|
262
|
+
|
|
263
|
+
if [ "$CHANGED" = false ]; then
|
|
195
264
|
echo "update-issue-progress: no change for #$ISSUE_NUM (idempotent no-op)" >&2
|
|
196
265
|
exit 0
|
|
197
266
|
fi
|
|
198
267
|
|
|
199
268
|
# Apply.
|
|
200
|
-
if ! ma_outbound_gate "$TMP_NEW"; then
|
|
201
|
-
echo "update-issue-progress: outbound gate refused the new body; #$ISSUE_NUM left unchanged" >&2
|
|
202
|
-
exit 7
|
|
203
|
-
fi
|
|
204
269
|
if ! gh issue edit "$ISSUE_NUM" --repo "$ORG_REPO" --body-file "$TMP_NEW" >/dev/null; then
|
|
205
270
|
echo "update-issue-progress: gh issue edit failed for #$ISSUE_NUM" >&2
|
|
206
271
|
exit 4
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
import { readFileSync } from "node:fs";
|
|
29
29
|
import { planOverlaps } from "./_plan-overlap.mjs";
|
|
30
|
+
import { sectionBody, tableDataRows } from "../lib/analysis-sections.mjs";
|
|
30
31
|
|
|
31
32
|
const ALLOWED_TYPES = new Set(["test", "code", "refactor", "docs", "chore"]);
|
|
32
33
|
const TASK_ID_RE = /^T\d+$/;
|
|
@@ -35,6 +36,85 @@ const REVIEW_FOCUS_MAX = 5;
|
|
|
35
36
|
const SPEC_RATIO = 4;
|
|
36
37
|
const SPEC_MIN_CHARS = 400;
|
|
37
38
|
|
|
39
|
+
const FIGMA_LINK = /\bfigma\.com\/(?:design|file|proto)\//i;
|
|
40
|
+
const DESIGN_VALUE = /\b\d+(?:\.\d+)?\s?(?:px|pt)\b/i;
|
|
41
|
+
const INVENTORY_KEYWORDS = ["Bileşen Envanteri", "Component Inventory"];
|
|
42
|
+
const UI_FILE = /(?:View|Screen|Scene|Content|Cell|Sheet)\.(?:swift|kt)$|\.figma\.(?:swift|kt)$/;
|
|
43
|
+
const MAPPINGS = new Set(["code-connect", "new", "best-fit"]);
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The analysis Section 6 rows: the component name (first cell) and whether it
|
|
47
|
+
* exists already (second cell, yes / no; anything else is unknown).
|
|
48
|
+
*/
|
|
49
|
+
function inventoryComponents(specText) {
|
|
50
|
+
const body = sectionBody(String(specText).split("\n"), INVENTORY_KEYWORDS, 2);
|
|
51
|
+
if (!body) return [];
|
|
52
|
+
return tableDataRows(body)
|
|
53
|
+
.map((row) => {
|
|
54
|
+
const cells = row.split("|").map((c) => c.replace(/[`*]/g, "").trim());
|
|
55
|
+
const existing = (cells[2] || "").toLowerCase();
|
|
56
|
+
return {
|
|
57
|
+
name: cells[1] ?? "",
|
|
58
|
+
existing: /^(yes|evet)\b/.test(existing)
|
|
59
|
+
? true
|
|
60
|
+
: /^(no|hayır|hayir)\b/.test(existing)
|
|
61
|
+
? false
|
|
62
|
+
: null,
|
|
63
|
+
};
|
|
64
|
+
})
|
|
65
|
+
.filter((r) => r.name && !/^<.*>$/.test(r.name));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Errors and warnings for the plan's UI tasks against Section 6: a task that
|
|
70
|
+
* touches a view file names its component, and the name is a Section 6 row.
|
|
71
|
+
*/
|
|
72
|
+
function designCitations(tasks, components) {
|
|
73
|
+
const errors = [];
|
|
74
|
+
const warnings = [];
|
|
75
|
+
if (!components.length || !Array.isArray(tasks)) return { errors, warnings };
|
|
76
|
+
const known = new Map(components.map((c) => [c.name, c.existing]));
|
|
77
|
+
for (const t of tasks) {
|
|
78
|
+
if (!t || typeof t !== "object") continue;
|
|
79
|
+
const d = t.design;
|
|
80
|
+
if (d !== undefined) {
|
|
81
|
+
if (!d || typeof d.component !== "string" || !MAPPINGS.has(d.mapping)) {
|
|
82
|
+
errors.push(`tasks ${t.id}.design: needs {component, mapping: code-connect|new|best-fit}`);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const name = d.component.trim();
|
|
86
|
+
if (!known.has(name)) {
|
|
87
|
+
errors.push(
|
|
88
|
+
`tasks ${t.id}.design.component: "${d.component}" is not a Section 6 row; use the inventory's name verbatim, never a sound-alike`,
|
|
89
|
+
);
|
|
90
|
+
} else if (d.mapping === "new" && known.get(name) === true) {
|
|
91
|
+
errors.push(
|
|
92
|
+
`tasks ${t.id}.design: "${name}" exists already per Section 6; reuse it, do not build it new`,
|
|
93
|
+
);
|
|
94
|
+
} else if (d.mapping === "code-connect" && known.get(name) === false) {
|
|
95
|
+
errors.push(
|
|
96
|
+
`tasks ${t.id}.design: "${name}" does not exist yet per Section 6, so it has no Code Connect mapping`,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
if (d.mapping === "best-fit") {
|
|
100
|
+
warnings.push(
|
|
101
|
+
`tasks ${t.id}: best-fit component needs a Risk row; Phase 3 gates it review_blocking`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const ui = (Array.isArray(t.files) ? t.files : []).some(
|
|
107
|
+
(f) => typeof f === "string" && UI_FILE.test(f),
|
|
108
|
+
);
|
|
109
|
+
if (ui && (t.type === "code" || t.type === "refactor")) {
|
|
110
|
+
errors.push(
|
|
111
|
+
`tasks ${t.id}: touches a UI file but cites no Section 6 component (design.component)`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return { errors, warnings };
|
|
116
|
+
}
|
|
117
|
+
|
|
38
118
|
function specPath() {
|
|
39
119
|
const i = process.argv.indexOf("--spec");
|
|
40
120
|
return i > 0 ? process.argv[i + 1] : null;
|
|
@@ -241,11 +321,35 @@ async function main() {
|
|
|
241
321
|
const spec = specPath();
|
|
242
322
|
if (spec) {
|
|
243
323
|
let specChars = 0;
|
|
324
|
+
let specText = "";
|
|
244
325
|
try {
|
|
245
|
-
|
|
326
|
+
specText = readFileSync(spec, "utf-8");
|
|
327
|
+
specChars = specText.length;
|
|
246
328
|
} catch {
|
|
247
329
|
warnings.push(`spec not readable: ${spec}`);
|
|
248
330
|
}
|
|
331
|
+
// With a design behind the plan, values come from its tokens, never from a
|
|
332
|
+
// number someone read off a screenshot.
|
|
333
|
+
if (FIGMA_LINK.test(specText) && Array.isArray(parsed.tasks)) {
|
|
334
|
+
for (const t of parsed.tasks) {
|
|
335
|
+
const text = [
|
|
336
|
+
t?.title,
|
|
337
|
+
t?.description,
|
|
338
|
+
...(Array.isArray(t?.acceptanceCriteria) ? t.acceptanceCriteria : []),
|
|
339
|
+
]
|
|
340
|
+
.filter((v) => typeof v === "string")
|
|
341
|
+
.join("\n");
|
|
342
|
+
const m = text.match(DESIGN_VALUE);
|
|
343
|
+
if (m) {
|
|
344
|
+
errors.push(
|
|
345
|
+
`tasks ${t.id}: "${m[0]}" is a raw design value; name the design token (Spacing.*, Radius.*, a type style) the analysis mapped instead`,
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
const cited = designCitations(parsed.tasks, inventoryComponents(specText));
|
|
351
|
+
errors.push(...cited.errors);
|
|
352
|
+
warnings.push(...cited.warnings);
|
|
249
353
|
if (specChars >= SPEC_MIN_CHARS && raw.length > specChars * SPEC_RATIO) {
|
|
250
354
|
warnings.push(
|
|
251
355
|
`plan is ${Math.round(raw.length / specChars)}x the length of its spec; record decisions, not a transcript`,
|
|
@@ -13,6 +13,15 @@
|
|
|
13
13
|
// cat triage.json | node pipeline/scripts/validate-triage.mjs -
|
|
14
14
|
// [--reviewers a.json,b.json] # every reviewer cannotVerify row needs a triage row
|
|
15
15
|
// [--reviewers-dir <dir>] # same, for every reviewer-*.json in <dir>
|
|
16
|
+
// [--anonymized] # the verdict was judged on anonymized input:
|
|
17
|
+
// # no finding may carry `reviewer`, labels only
|
|
18
|
+
//
|
|
19
|
+
// An accepted finding names its source as `reviewer` (a model family),
|
|
20
|
+
// `foundBy` (an anonymized "Source A" label, or "security-auditor") or `tag`
|
|
21
|
+
// (a deterministic gate finding). Triage judges anonymized findings and
|
|
22
|
+
// carries `foundBy` through; anonymize-findings.mjs --restore writes the
|
|
23
|
+
// family back afterwards, so a `reviewer` in the verdict itself means the
|
|
24
|
+
// judge was told whose findings it graded.
|
|
16
25
|
//
|
|
17
26
|
// Exit codes:
|
|
18
27
|
// 0 - valid, safe to act on
|
|
@@ -27,6 +36,8 @@ import { readdirSync, readFileSync } from "node:fs";
|
|
|
27
36
|
import { join } from "node:path";
|
|
28
37
|
|
|
29
38
|
const ALLOWED_REVIEWERS = new Set(["fable", "opus", "sonnet", "gpt"]);
|
|
39
|
+
const FOUND_BY_RE = /^(Source [A-Z]|security-auditor)$/;
|
|
40
|
+
const ANONYMIZED = process.argv.includes("--anonymized");
|
|
30
41
|
const ALLOWED_SEVERITIES = new Set(["blocking", "important", "suggestion"]);
|
|
31
42
|
const ALLOWED_CONSENSUS_VERDICTS = new Set([
|
|
32
43
|
"unanimous-pass",
|
|
@@ -174,11 +185,33 @@ function validateVerification(v, label, errors) {
|
|
|
174
185
|
}
|
|
175
186
|
}
|
|
176
187
|
|
|
188
|
+
/** The finding's source kind, or null when none is valid. */
|
|
189
|
+
function sourceOf(f) {
|
|
190
|
+
if (f?.foundBy !== undefined) return FOUND_BY_RE.test(String(f.foundBy)) ? "label" : null;
|
|
191
|
+
if (typeof f?.tag === "string" && f.tag) return "tag";
|
|
192
|
+
return ALLOWED_REVIEWERS.has(f?.reviewer) ? "reviewer" : null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** With --anonymized: every finding that names a reviewer, by list and index. */
|
|
196
|
+
function identityLeaks(parsed) {
|
|
197
|
+
const out = [];
|
|
198
|
+
(parsed.accepted || []).forEach((f, i) => {
|
|
199
|
+
if (f && Object.hasOwn(f, "reviewer")) out.push(`accepted[${i}]`);
|
|
200
|
+
});
|
|
201
|
+
for (const key of ["deferred", "rejected"]) {
|
|
202
|
+
(parsed[key] || []).forEach((e, i) => {
|
|
203
|
+
if (e?.finding && Object.hasOwn(e.finding, "reviewer")) out.push(`${key}[${i}].finding`);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
return out;
|
|
207
|
+
}
|
|
208
|
+
|
|
177
209
|
function validateAccepted(f, i, errors) {
|
|
178
210
|
validateRawFinding(f, `accepted[${i}]`, errors);
|
|
179
|
-
|
|
211
|
+
const source = sourceOf(f);
|
|
212
|
+
if (source === null) {
|
|
180
213
|
errors.push(
|
|
181
|
-
`accepted[${i}]:
|
|
214
|
+
`accepted[${i}]: name the source as foundBy ("Source A".."Source Z" or "security-auditor"), tag, or reviewer fable|opus|sonnet|gpt (got reviewer ${JSON.stringify(f?.reviewer)}, foundBy ${JSON.stringify(f?.foundBy)})`,
|
|
182
215
|
);
|
|
183
216
|
}
|
|
184
217
|
if (typeof f.fix !== "string" || f.fix.length < 4) {
|
|
@@ -279,6 +312,13 @@ function validate(raw) {
|
|
|
279
312
|
parsed.accepted.forEach((f, i) => validateAccepted(f, i, errors));
|
|
280
313
|
parsed.deferred.forEach((f, i) => validateClassified(f, "deferred", i, errors));
|
|
281
314
|
parsed.rejected.forEach((f, i) => validateClassified(f, "rejected", i, errors));
|
|
315
|
+
if (ANONYMIZED) {
|
|
316
|
+
for (const at of identityLeaks(parsed)) {
|
|
317
|
+
errors.push(
|
|
318
|
+
`${at}: carries reviewer in an anonymized verdict; carry foundBy through, the family is restored after validation`,
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
282
322
|
|
|
283
323
|
// Every requirement a reviewer could not verify from the diff is settled here:
|
|
284
324
|
// checked in the unchanged code (verified), turned into a finding, or ruled
|
|
@@ -7,6 +7,13 @@
|
|
|
7
7
|
* /multi-agent:jira, /multi-agent:issue and Phase 0 run;
|
|
8
8
|
* title, type, status, score, blockers, warnings, summary
|
|
9
9
|
* and a description excerpt, scrubbed
|
|
10
|
+
* reach how far each open GitHub pull request reaches: its
|
|
11
|
+
* changed files (`gh pr view --json files`) looked up in
|
|
12
|
+
* the repository's own code graph, and the files that
|
|
13
|
+
* depend on them counted (graph-affected, depth 2). A
|
|
14
|
+
* repository without a graph, a Bitbucket pull request or
|
|
15
|
+
* a name that is not owner/repo is listed with its reason
|
|
16
|
+
* and costs no gh call
|
|
10
17
|
* prs open pull requests on GitHub where you are asked to
|
|
11
18
|
* review or are the author (`gh search prs`, the queries
|
|
12
19
|
* /multi-agent:review lists). Bitbucket is reported as a
|
|
@@ -34,6 +41,7 @@
|
|
|
34
41
|
* Usage:
|
|
35
42
|
* work-items.mjs maturity <key>
|
|
36
43
|
* work-items.mjs prs
|
|
44
|
+
* work-items.mjs reach
|
|
37
45
|
* work-items.mjs verdict <task-id> [--post]
|
|
38
46
|
*
|
|
39
47
|
* MA_GH_BIN stands in for gh, MA_ISSUE_FETCHER and MA_POST_PR_REVIEW for the
|
|
@@ -57,6 +65,8 @@ import { runMain } from "../lib/fatal.mjs";
|
|
|
57
65
|
import { invokedDirectly } from "../lib/invoked-directly.mjs";
|
|
58
66
|
import { resolveTokenAsync } from "../lib/credential-resolve.mjs";
|
|
59
67
|
import { homedir } from "node:os";
|
|
68
|
+
import { affected } from "./graph-affected.mjs";
|
|
69
|
+
import { loadGraph } from "./graph-query.mjs";
|
|
60
70
|
|
|
61
71
|
export const CONTRACT_VERSION = "1.1.0";
|
|
62
72
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
@@ -349,6 +359,113 @@ export async function pullRequests({ env = process.env, redact = false } = {}) {
|
|
|
349
359
|
return { contractVersion: CONTRACT_VERSION, items, sources };
|
|
350
360
|
}
|
|
351
361
|
|
|
362
|
+
const REACH_MAX = 20;
|
|
363
|
+
const REACH_PARALLEL = 3;
|
|
364
|
+
const REACH_DEPTH = 2;
|
|
365
|
+
const FILES_MAX = 300;
|
|
366
|
+
const NAME_PART = /^(?!\.{1,2}$)[A-Za-z0-9._-]{1,100}$/;
|
|
367
|
+
|
|
368
|
+
/** owner/repo with two plain parts; anything else never reaches gh. */
|
|
369
|
+
function plainRepo(repo) {
|
|
370
|
+
const parts = String(repo).split("/");
|
|
371
|
+
return parts.length === 2 && parts.every((x) => NAME_PART.test(x));
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async function inBatches(items, size, fn) {
|
|
375
|
+
const out = [];
|
|
376
|
+
for (let i = 0; i < items.length; i += size) {
|
|
377
|
+
out.push(...(await Promise.all(items.slice(i, i + size).map(fn))));
|
|
378
|
+
}
|
|
379
|
+
return out;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/** The changed file paths of one GitHub pull request, or null when gh could not say. */
|
|
383
|
+
async function changedFiles(env, repo, number) {
|
|
384
|
+
const gh = env.MA_GH_BIN || "gh";
|
|
385
|
+
const r = await spawnAsync(
|
|
386
|
+
gh,
|
|
387
|
+
["pr", "view", String(number), "--repo", repo, "--json", "files"],
|
|
388
|
+
{
|
|
389
|
+
env,
|
|
390
|
+
timeoutMs: TIMEOUT_MS,
|
|
391
|
+
},
|
|
392
|
+
);
|
|
393
|
+
if (r.error || r.status !== 0) return null;
|
|
394
|
+
try {
|
|
395
|
+
const files = JSON.parse(r.stdout)?.files;
|
|
396
|
+
return (Array.isArray(files) ? files : [])
|
|
397
|
+
.map((f) => f?.path)
|
|
398
|
+
.filter((path) => typeof path === "string" && path.length <= 1024)
|
|
399
|
+
.slice(0, FILES_MAX);
|
|
400
|
+
} catch {
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* How far each open pull request reaches, widest first. Changed paths are
|
|
407
|
+
* used only as keys into the local graph: nothing is read from disk by them
|
|
408
|
+
* and none is echoed back, only counts.
|
|
409
|
+
*/
|
|
410
|
+
export async function prReach({ env = process.env, list = null } = {}) {
|
|
411
|
+
const prs = list ?? (await pullRequests({ env }));
|
|
412
|
+
const envByLogin = new Map();
|
|
413
|
+
for (const a of await ghAccounts(env)) {
|
|
414
|
+
const aEnv = await accountEnv(env, a);
|
|
415
|
+
if (aEnv) envByLogin.set(a.login, aEnv);
|
|
416
|
+
}
|
|
417
|
+
const knowledge = join(env.HOME || homedir(), ".claude", "knowledge");
|
|
418
|
+
const graphs = new Map();
|
|
419
|
+
const graphFor = (project) => {
|
|
420
|
+
if (!graphs.has(project)) {
|
|
421
|
+
const path = join(knowledge, project, "code-graph.json");
|
|
422
|
+
let graph;
|
|
423
|
+
try {
|
|
424
|
+
graph = existsSync(path) ? loadGraph(path) : null;
|
|
425
|
+
} catch {
|
|
426
|
+
graph = null;
|
|
427
|
+
}
|
|
428
|
+
graphs.set(project, graph);
|
|
429
|
+
}
|
|
430
|
+
return graphs.get(project);
|
|
431
|
+
};
|
|
432
|
+
const candidates = prs.items
|
|
433
|
+
.filter((p) => p.source !== "github" || plainRepo(p.repo))
|
|
434
|
+
.slice(0, REACH_MAX);
|
|
435
|
+
const rows = await inBatches(candidates, REACH_PARALLEL, async (p) => {
|
|
436
|
+
const row = { source: p.source, repo: p.repo, number: p.number, project: null };
|
|
437
|
+
if (p.source !== "github") return { ...row, reach: null, reason: "not measured off GitHub" };
|
|
438
|
+
const project = p.repo.split("/")[1];
|
|
439
|
+
const graph = graphFor(project);
|
|
440
|
+
if (!graph) return { ...row, reach: null, reason: `no code graph for ${project}` };
|
|
441
|
+
const files = await changedFiles(envByLogin.get(p.account) ?? env, p.repo, p.number);
|
|
442
|
+
if (!files) return { ...row, project, reach: null, reason: "gh could not list its files" };
|
|
443
|
+
const ids = new Set(graph.nodes.filter((n) => n.kind === "file").map((n) => n.id));
|
|
444
|
+
const seeds = files.map((f) => `file:${f}`).filter((id) => ids.has(id));
|
|
445
|
+
const byId = new Map(graph.nodes.map((n) => [n.id, n]));
|
|
446
|
+
const dependents = seeds.length
|
|
447
|
+
? affected({ graph, seeds: seeds.map((id) => byId.get(id)), depth: REACH_DEPTH })
|
|
448
|
+
: [];
|
|
449
|
+
const reachedFiles = new Set(
|
|
450
|
+
dependents.filter((d) => d.node?.kind === "file").map((d) => d.id),
|
|
451
|
+
);
|
|
452
|
+
return {
|
|
453
|
+
...row,
|
|
454
|
+
project,
|
|
455
|
+
changed: files.length,
|
|
456
|
+
inGraph: seeds.length,
|
|
457
|
+
reach: reachedFiles.size,
|
|
458
|
+
};
|
|
459
|
+
});
|
|
460
|
+
const updated = new Map(prs.items.map((p) => [`${p.repo}#${p.number}`, p.updatedAt ?? ""]));
|
|
461
|
+
rows.sort(
|
|
462
|
+
(a, b) =>
|
|
463
|
+
(b.reach ?? -1) - (a.reach ?? -1) ||
|
|
464
|
+
(updated.get(`${a.repo}#${a.number}`) < updated.get(`${b.repo}#${b.number}`) ? 1 : -1),
|
|
465
|
+
);
|
|
466
|
+
return { contractVersion: CONTRACT_VERSION, depth: REACH_DEPTH, items: rows };
|
|
467
|
+
}
|
|
468
|
+
|
|
352
469
|
function stateOf(taskId) {
|
|
353
470
|
const { found, records } = selectRuns({ taskId });
|
|
354
471
|
if (!found || !records.length) throw new WorkError("no-run", "no such run");
|
|
@@ -449,11 +566,12 @@ async function main(argv) {
|
|
|
449
566
|
let out;
|
|
450
567
|
if (cmd === "maturity" && arg) out = await issueMaturity(arg);
|
|
451
568
|
else if (cmd === "prs" && !arg) out = await pullRequests();
|
|
569
|
+
else if (cmd === "reach" && !arg) out = await prReach();
|
|
452
570
|
else if (cmd === "verdict" && arg && (flag === undefined || flag === "--post")) {
|
|
453
571
|
out = flag ? await postReviewVerdict(arg) : reviewVerdictPreview(arg);
|
|
454
572
|
} else {
|
|
455
573
|
process.stderr.write(
|
|
456
|
-
"usage: work-items.mjs maturity <key> | prs | verdict <task-id> [--post]\n",
|
|
574
|
+
"usage: work-items.mjs maturity <key> | prs | reach | verdict <task-id> [--post]\n",
|
|
457
575
|
);
|
|
458
576
|
return 2;
|
|
459
577
|
}
|