@mmerterden/multi-agent-pipeline 20.33.0 → 20.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +98 -1
- package/README.md +3 -1
- package/README.tr.md +2 -1
- package/docs/adr/0018-cross-repo-links-not-a-global-graph.md +43 -0
- package/docs/adr/README.md +2 -1
- package/docs/facts.json +5 -4
- package/docs/features.md +1 -1
- package/install/_dev-only-files.mjs +1 -0
- package/install/catalog-history.json +1 -1
- package/install/claude.mjs +24 -2
- package/install/templates/claude-hooks.json +34 -1
- package/manifest.json +393 -339
- package/package.json +3 -1
- package/pipeline/agents/bulk-reader.md +3 -0
- package/pipeline/agents/code-reviewer.md +9 -0
- package/pipeline/agents/explorer.md +4 -0
- package/pipeline/agents/plan-critic.md +4 -2
- package/pipeline/commands/multi-agent/SKILL.md +17 -136
- package/pipeline/commands/multi-agent/channels/SKILL.md +16 -298
- package/pipeline/commands/multi-agent/graph/SKILL.md +5 -2
- package/pipeline/commands/multi-agent/language/SKILL.md +3 -3
- package/pipeline/commands/multi-agent/office-on/SKILL.md +7 -3
- package/pipeline/commands/multi-agent/office-status/SKILL.md +3 -2
- package/pipeline/commands/multi-agent/resume/SKILL.md +8 -22
- package/pipeline/commands/multi-agent/review/SKILL.md +7 -131
- package/pipeline/commands/multi-agent/setup/SKILL.md +19 -224
- package/pipeline/contract/CHANGELOG.md +9 -0
- package/pipeline/contract/README.md +6 -2
- package/pipeline/contract/fixtures/error-invalid-request.json +1 -1
- package/pipeline/contract/fixtures/error-unauthorized.json +1 -1
- package/pipeline/contract/fixtures/error-unsigned.json +1 -1
- package/pipeline/contract/manifest.json +14 -1
- package/pipeline/contract/types/index.d.ts +24 -2
- package/pipeline/lib/json-file-lock.mjs +61 -15
- package/pipeline/lib/office-remote.mjs +228 -0
- package/pipeline/lib/qr-code.mjs +360 -0
- package/pipeline/lib/repo-hygiene.sh +1 -0
- package/pipeline/lib/run-completion.mjs +17 -0
- package/pipeline/multi-agent-refs/channels/generator.md +146 -0
- package/pipeline/multi-agent-refs/channels/issue-comment.md +1 -1
- package/pipeline/multi-agent-refs/channels/menu.md +55 -0
- package/pipeline/multi-agent-refs/channels/multi-repo.md +6 -0
- package/pipeline/multi-agent-refs/channels/preview.md +25 -0
- package/pipeline/multi-agent-refs/channels/reference.md +98 -0
- package/pipeline/multi-agent-refs/features/autopilot-circuit-breaker.md +1 -1
- package/pipeline/multi-agent-refs/features/code-graph.md +27 -0
- package/pipeline/multi-agent-refs/features/office-remote-protocol.md +166 -0
- package/pipeline/multi-agent-refs/features/office-threat-model.md +2 -2
- package/pipeline/multi-agent-refs/features/plan-critic.md +5 -2
- package/pipeline/multi-agent-refs/features/review-delta.md +4 -1
- package/pipeline/multi-agent-refs/features/scope-check.md +25 -1
- package/pipeline/multi-agent-refs/features/verify-by-test.md +10 -0
- package/pipeline/multi-agent-refs/keychain.md +7 -0
- package/pipeline/multi-agent-refs/knowledge.md +9 -0
- package/pipeline/multi-agent-refs/orchestrator/dispatcher.md +163 -0
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +6 -2
- package/pipeline/multi-agent-refs/phases/phase-1-plan.md +8 -10
- package/pipeline/multi-agent-refs/phases/phase-2-dev.md +18 -20
- package/pipeline/multi-agent-refs/phases/phase-3-review.md +20 -14
- package/pipeline/multi-agent-refs/phases/phase-4-commit.md +8 -7
- package/pipeline/multi-agent-refs/phases/phase-5-report.md +6 -3
- package/pipeline/multi-agent-refs/picker-contract.md +1 -1
- package/pipeline/multi-agent-refs/resume/details.md +35 -0
- package/pipeline/multi-agent-refs/review/inputs.md +87 -0
- package/pipeline/multi-agent-refs/review/module-guides.md +25 -0
- package/pipeline/multi-agent-refs/review/valven.md +42 -0
- package/pipeline/multi-agent-refs/rules.md +3 -2
- package/pipeline/multi-agent-refs/setup/accounts.md +27 -0
- package/pipeline/multi-agent-refs/setup/app-store-connect.md +5 -0
- package/pipeline/multi-agent-refs/setup/firebase.md +5 -0
- package/pipeline/multi-agent-refs/setup/google-play.md +4 -0
- package/pipeline/multi-agent-refs/setup/hooks-and-plugins.md +21 -0
- package/pipeline/multi-agent-refs/setup/language.md +46 -0
- package/pipeline/multi-agent-refs/setup/multi-repo-group.md +51 -0
- package/pipeline/multi-agent-refs/setup/summaries.md +63 -0
- package/pipeline/multi-agent-refs/setup/token-save-flow.md +51 -0
- 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 +304 -55
- package/pipeline/office/toolkit.json +1 -1
- package/pipeline/rules/response-shape.md +6 -7
- package/pipeline/schemas/agent-state.schema.json +36 -0
- package/pipeline/schemas/phases.json +4 -4
- package/pipeline/schemas/plan-critique.schema.json +9 -2
- package/pipeline/schemas/planning-output.schema.json +43 -2
- package/pipeline/schemas/pr-reach.schema.json +55 -0
- package/pipeline/schemas/prefs.schema.json +37 -8
- package/pipeline/schemas/reviewer-output.schema.json +20 -2
- package/pipeline/schemas/token-budget.json +4 -4
- package/pipeline/schemas/triage-output.schema.json +32 -3
- package/pipeline/schemas/unattended-policy.json +2 -1
- package/pipeline/scripts/_graph-analysis.mjs +270 -0
- package/pipeline/scripts/_guard_rules.py +449 -1
- package/pipeline/scripts/_memory-policy.mjs +165 -0
- package/pipeline/scripts/_secret-scan.mjs +105 -0
- package/pipeline/scripts/_test-gaming.mjs +36 -0
- package/pipeline/scripts/_unified-diff.mjs +2 -2
- 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/capture-flush.sh +12 -2
- package/pipeline/scripts/check-read-size.sh +9 -0
- package/pipeline/scripts/code-graph-rules/android.json +8 -3
- package/pipeline/scripts/contract-server.mjs +71 -16
- package/pipeline/scripts/debt-ledger.mjs +323 -0
- package/pipeline/scripts/file-context.mjs +261 -0
- package/pipeline/scripts/file-context.sh +20 -0
- package/pipeline/scripts/gate-ledger.mjs +144 -21
- package/pipeline/scripts/graph-links.mjs +230 -0
- package/pipeline/scripts/graph-query.mjs +133 -1
- package/pipeline/scripts/graph-report.mjs +65 -0
- package/pipeline/scripts/learn-from-transcripts.mjs +12 -48
- package/pipeline/scripts/learnings-ledger.mjs +143 -24
- package/pipeline/scripts/office-build.mjs +276 -5
- package/pipeline/scripts/office-control.mjs +70 -10
- package/pipeline/scripts/office-pair.mjs +407 -0
- package/pipeline/scripts/office-remote-client.mjs +463 -0
- package/pipeline/scripts/phase-tracker.sh +8 -0
- package/pipeline/scripts/phase0-exit-gate.mjs +40 -37
- package/pipeline/scripts/phone-devices.mjs +9 -1
- package/pipeline/scripts/plan-critique-gate.mjs +11 -2
- 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/rework-rulings.mjs +106 -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 +213 -2
- 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/triage-memory.mjs +64 -37
- package/pipeline/scripts/update-check.sh +7 -1
- package/pipeline/scripts/update-issue-progress.sh +73 -8
- package/pipeline/scripts/validate-planning.mjs +172 -4
- package/pipeline/scripts/validate-reviewer.mjs +20 -0
- package/pipeline/scripts/validate-triage.mjs +149 -4
- 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 +9 -9
- package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +5 -5
- package/pipeline/skills/shared/core/google-play-compliance/references/rules.yml +1 -1
- package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-graph/SKILL.md +5 -2
- package/pipeline/skills/shared/core/multi-agent-language/SKILL.md +4 -4
- package/pipeline/skills/shared/core/multi-agent-office-status/SKILL.md +2 -2
- package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +2 -0
- package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +4 -4
- 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
|
@@ -10,21 +10,38 @@
|
|
|
10
10
|
* Usage:
|
|
11
11
|
* git diff --name-only <base>...HEAD | node scope-check-gate.mjs --check .pipeline/scope-check.json --diff-files -
|
|
12
12
|
* node scope-check-gate.mjs --check scope-check.json --diff-files files.txt [--advisory]
|
|
13
|
+
* [--patch <unified diff> | --repo <dir> --patch-range <a>...<b>]
|
|
14
|
+
* [--rejected <triage-output.json> --repo <dir> --rework-base <rev>]
|
|
13
15
|
*
|
|
14
|
-
* Output (stdout, JSON): {ok, justified, unjustified[], unlisted[], notDone}
|
|
16
|
+
* Output (stdout, JSON): {ok, justified, unjustified[], unlisted[], notDone, formatOnly[]}
|
|
15
17
|
* unjustified = in the diff, missing from files[] (or with an empty reason)
|
|
16
18
|
* unlisted = in files[], not in the diff (stale entry; reported, never fatal)
|
|
19
|
+
* formatOnly = with --patch, hunks whose removed and added lines differ only
|
|
20
|
+
* in whitespace: a reformat of lines the task did not need to
|
|
21
|
+
* touch. Reported, never fatal. --patch-range reads that diff
|
|
22
|
+
* with git itself, its output format pinned, so a caller needs
|
|
23
|
+
* no file or process substitution for it.
|
|
24
|
+
* rejectedTouched = with --rejected and --rework-base, findings triage
|
|
25
|
+
* rejected whose lines a rework hunk (rework base against the
|
|
26
|
+
* working tree) changed: Phase 2 acts on accepted findings
|
|
27
|
+
* only, so a rework that edits a rejected finding's lines is
|
|
28
|
+
* "fixing" a finding triage ruled out. Reported, never fatal.
|
|
17
29
|
*
|
|
18
30
|
* Exit codes: 0 every diff file justified (or --advisory), 1 otherwise or on
|
|
19
31
|
* unreadable input, 64 usage.
|
|
20
32
|
*/
|
|
21
33
|
|
|
34
|
+
import { execFileSync } from "node:child_process";
|
|
22
35
|
import { readFileSync } from "node:fs";
|
|
23
36
|
|
|
24
37
|
import { normalizeFile } from "./_fingerprint.mjs";
|
|
38
|
+
import { parseUnifiedDiff } from "./_unified-diff.mjs";
|
|
25
39
|
|
|
26
40
|
const USAGE =
|
|
27
|
-
"usage: scope-check-gate.mjs --check <scope-check.json> --diff-files <path|-> [--advisory]";
|
|
41
|
+
"usage: scope-check-gate.mjs --check <scope-check.json> --diff-files <path|-> [--advisory] [--patch <diff> | --repo <dir> --patch-range <a>...<b>] [--rejected <triage.json> --repo <dir> --rework-base <rev>]";
|
|
42
|
+
|
|
43
|
+
const REV = /^[A-Za-z0-9_./~^@{}-]+$/;
|
|
44
|
+
const NEAR = 2;
|
|
28
45
|
|
|
29
46
|
function parseArgs(argv) {
|
|
30
47
|
const opts = {};
|
|
@@ -33,15 +50,188 @@ function parseArgs(argv) {
|
|
|
33
50
|
if (a === "--check") opts.check = argv[++i];
|
|
34
51
|
else if (a === "--diff-files") opts.diffFiles = argv[++i];
|
|
35
52
|
else if (a === "--advisory") opts.advisory = true;
|
|
53
|
+
else if (a === "--patch") opts.patch = argv[++i];
|
|
54
|
+
else if (a === "--patch-range") opts.patchRange = argv[++i];
|
|
55
|
+
else if (a === "--repo") opts.repo = argv[++i];
|
|
56
|
+
else if (a === "--rejected") opts.rejected = argv[++i];
|
|
57
|
+
else if (a === "--rework-base") opts.reworkBase = argv[++i];
|
|
36
58
|
else return null;
|
|
37
59
|
}
|
|
60
|
+
for (const rev of [opts.patchRange, opts.reworkBase]) {
|
|
61
|
+
if (rev !== undefined && (!REV.test(rev) || rev.startsWith("-"))) return null;
|
|
62
|
+
}
|
|
63
|
+
if (Boolean(opts.rejected) !== Boolean(opts.reworkBase)) return null;
|
|
38
64
|
return opts.check && opts.diffFiles ? opts : null;
|
|
39
65
|
}
|
|
40
66
|
|
|
67
|
+
/** The diff of a revision range (or a revision against the working tree), in the one shape the hunk readers take. */
|
|
68
|
+
function rangeDiff(repo, range, unified = []) {
|
|
69
|
+
return execFileSync(
|
|
70
|
+
"git",
|
|
71
|
+
[
|
|
72
|
+
"-C",
|
|
73
|
+
repo || ".",
|
|
74
|
+
"-c",
|
|
75
|
+
"core.quotePath=false",
|
|
76
|
+
"diff",
|
|
77
|
+
"--no-ext-diff",
|
|
78
|
+
"--no-color",
|
|
79
|
+
"--no-textconv",
|
|
80
|
+
"--src-prefix=a/",
|
|
81
|
+
"--dst-prefix=b/",
|
|
82
|
+
...unified,
|
|
83
|
+
range,
|
|
84
|
+
"--",
|
|
85
|
+
],
|
|
86
|
+
{ encoding: "utf8", maxBuffer: 64 * 1024 * 1024, timeout: 30_000 },
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
41
90
|
function readText(path) {
|
|
42
91
|
return path === "-" ? readFileSync(0, "utf-8") : readFileSync(path, "utf-8");
|
|
43
92
|
}
|
|
44
93
|
|
|
94
|
+
const INDENT_SENSITIVE = /(\.(py|pyi|ya?ml|coffee|haml|slim|pug)|(^|\/)(GNU)?[Mm]akefile)$/;
|
|
95
|
+
|
|
96
|
+
const LINE_COMMENT = /(\.(py|pyi|ya?ml|sh|bash|zsh|rb|toml|coffee)|(^|\/)(GNU)?[Mm]akefile)$/;
|
|
97
|
+
// Languages where a space next to a bracket, comma, semicolon or colon never
|
|
98
|
+
// carries meaning. YAML, shell, Makefile and CSS are left out on purpose.
|
|
99
|
+
const TIGHT_LANGS =
|
|
100
|
+
/\.(js|jsx|mjs|cjs|ts|tsx|java|go|cs|swift|kt|kts|json|rs|dart|scala|py|pyi|c|h|cc|cpp|hpp|m|mm)$/;
|
|
101
|
+
const LEADING_OPERATOR_LANGS = /\.(kt|kts|swift|go|scala)$/;
|
|
102
|
+
const BREAK_KEEPERS = /\b(return|throw|yield|break|continue)$/;
|
|
103
|
+
const LEADING_OPERATOR = /^[+\-*/%&|^.?]/;
|
|
104
|
+
const TIGHT = /\s*([(){}[\],;:])\s*/g;
|
|
105
|
+
|
|
106
|
+
/** One line with string literals and comments verbatim and every other whitespace run as one space. */
|
|
107
|
+
function foldLine(line, hashComments) {
|
|
108
|
+
const out = [];
|
|
109
|
+
let quote = null;
|
|
110
|
+
let gap = false;
|
|
111
|
+
for (let i = 0; i < line.length; i++) {
|
|
112
|
+
const c = line[i];
|
|
113
|
+
if (quote) {
|
|
114
|
+
out.push(c);
|
|
115
|
+
if (c === "\\") out.push(line[++i] ?? "");
|
|
116
|
+
else if (c === quote) quote = null;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (c === " " || c === "\t" || c === "\r") {
|
|
120
|
+
gap = true;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (gap && out.length) out.push(" ");
|
|
124
|
+
gap = false;
|
|
125
|
+
if ((c === "/" && line[i + 1] === "/") || (hashComments && c === "#")) {
|
|
126
|
+
out.push(line.slice(i));
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
if (c === '"' || c === "'" || c === "`") quote = c;
|
|
130
|
+
out.push(c);
|
|
131
|
+
}
|
|
132
|
+
return out.join("");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* The layout-free form of some lines. Each line is folded on its own (linear
|
|
137
|
+
* in the hunk), then the lines are joined by a space, except that a break
|
|
138
|
+
* after return, throw, yield, break or continue, and in Kotlin, Swift, Go or
|
|
139
|
+
* Scala a break before a leading operator, stays a break. Space next to
|
|
140
|
+
* brackets, commas, semicolons and colons is dropped only in TIGHT_LANGS, and
|
|
141
|
+
* never in a hunk that holds a C preprocessor line.
|
|
142
|
+
*/
|
|
143
|
+
function outsideStrings(text, file = "") {
|
|
144
|
+
const hashComments = LINE_COMMENT.test(file);
|
|
145
|
+
const lines = text.split("\n").map((l) => foldLine(l, hashComments));
|
|
146
|
+
let joined = "";
|
|
147
|
+
lines.forEach((line, i) => {
|
|
148
|
+
if (i === 0) {
|
|
149
|
+
joined = line;
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const keepBreak =
|
|
153
|
+
BREAK_KEEPERS.test(lines[i - 1]) ||
|
|
154
|
+
(LEADING_OPERATOR_LANGS.test(file) && LEADING_OPERATOR.test(line));
|
|
155
|
+
joined += (keepBreak ? "\u23ce" : " ") + line;
|
|
156
|
+
});
|
|
157
|
+
const preprocessor = lines.some((l) => l.startsWith("#")) && !hashComments;
|
|
158
|
+
const tight = TIGHT_LANGS.test(file) && !preprocessor;
|
|
159
|
+
return (tight ? joined.replace(TIGHT, "$1") : joined).trim();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const indentOf = (line) => /^[ \t]*/.exec(line)[0];
|
|
163
|
+
|
|
164
|
+
/** True when the removed and added lines differ only in layout. */
|
|
165
|
+
function layoutOnly(file, removed, added) {
|
|
166
|
+
if (!removed.length || !added.length) return false;
|
|
167
|
+
if (INDENT_SENSITIVE.test(file)) {
|
|
168
|
+
if (removed.length !== added.length) return false;
|
|
169
|
+
return removed.every(
|
|
170
|
+
(r, i) =>
|
|
171
|
+
indentOf(r.text) === indentOf(added[i].text) &&
|
|
172
|
+
outsideStrings(r.text, file) === outsideStrings(added[i].text, file),
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
const joined = (lines) => outsideStrings(lines.map((d) => d.text).join("\n"), file);
|
|
176
|
+
return joined(removed) === joined(added);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Hunks of a unified diff whose change is layout only: whitespace outside
|
|
181
|
+
* string literals, and in indentation-sensitive files never a leading indent.
|
|
182
|
+
*/
|
|
183
|
+
export function formatOnlyHunks(patch) {
|
|
184
|
+
const found = [];
|
|
185
|
+
for (const [file, diff] of parseUnifiedDiff(patch)) {
|
|
186
|
+
for (const h of diff.hunks) {
|
|
187
|
+
if (!h.removed.length && !h.added.length) continue;
|
|
188
|
+
if (layoutOnly(file, h.removed, h.added)) {
|
|
189
|
+
found.push({ file: normalizeFile(file), hunk: h.header });
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return found;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Rejected findings whose line a hunk of the rework diff changed. The diff
|
|
198
|
+
* runs from the reviewed tree, so a finding's line is read on the hunk's old
|
|
199
|
+
* side: a hunk that removes or rewrites lines within NEAR of it, or inserts
|
|
200
|
+
* next to it, touches it.
|
|
201
|
+
*/
|
|
202
|
+
export function rejectedTouched(triage, patch) {
|
|
203
|
+
const rejected = (Array.isArray(triage?.rejected) ? triage.rejected : [])
|
|
204
|
+
.map((r) => r?.finding)
|
|
205
|
+
.filter((f) => f && typeof f.file === "string" && Number.isInteger(f.line) && f.line > 0);
|
|
206
|
+
if (!rejected.length) return [];
|
|
207
|
+
const ranges = new Map();
|
|
208
|
+
for (const [file, diff] of parseUnifiedDiff(patch)) {
|
|
209
|
+
const list = [];
|
|
210
|
+
for (const h of diff.hunks) {
|
|
211
|
+
const m = /^@@ -(\d+)(?:,(\d+))?/.exec(h.header);
|
|
212
|
+
if (!m) continue;
|
|
213
|
+
const start = Number(m[1]);
|
|
214
|
+
const count = m[2] === undefined ? 1 : Number(m[2]);
|
|
215
|
+
list.push({ from: start - NEAR, to: start + Math.max(count, 1) - 1 + NEAR, hunk: h.header });
|
|
216
|
+
}
|
|
217
|
+
ranges.set(normalizeFile(file), list);
|
|
218
|
+
}
|
|
219
|
+
const out = [];
|
|
220
|
+
for (const f of rejected) {
|
|
221
|
+
const hit = (ranges.get(normalizeFile(f.file)) || []).find(
|
|
222
|
+
(r) => f.line >= r.from && f.line <= r.to,
|
|
223
|
+
);
|
|
224
|
+
if (hit)
|
|
225
|
+
out.push({
|
|
226
|
+
file: normalizeFile(f.file),
|
|
227
|
+
line: f.line,
|
|
228
|
+
issue: f.issue ?? null,
|
|
229
|
+
hunk: hit.hunk,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
return out;
|
|
233
|
+
}
|
|
234
|
+
|
|
45
235
|
export function evaluate(check, diffFiles) {
|
|
46
236
|
const files = Array.isArray(check?.files) ? check.files : [];
|
|
47
237
|
const reasons = new Map();
|
|
@@ -81,6 +271,27 @@ function main() {
|
|
|
81
271
|
return fail;
|
|
82
272
|
}
|
|
83
273
|
const result = evaluate(check, diffFiles);
|
|
274
|
+
if (opts.patch || opts.patchRange) {
|
|
275
|
+
try {
|
|
276
|
+
const patch = opts.patch ? readText(opts.patch) : rangeDiff(opts.repo, opts.patchRange);
|
|
277
|
+
result.formatOnly = formatOnlyHunks(patch);
|
|
278
|
+
} catch (err) {
|
|
279
|
+
result.formatOnly = [];
|
|
280
|
+
result.patchError = err.message;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (opts.rejected) {
|
|
284
|
+
try {
|
|
285
|
+
const triage = JSON.parse(readText(opts.rejected));
|
|
286
|
+
result.rejectedTouched = rejectedTouched(
|
|
287
|
+
triage,
|
|
288
|
+
rangeDiff(opts.repo, opts.reworkBase, ["-U0"]),
|
|
289
|
+
);
|
|
290
|
+
} catch (err) {
|
|
291
|
+
result.rejectedTouched = [];
|
|
292
|
+
result.rejectedError = err.message;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
84
295
|
process.stdout.write(JSON.stringify(result) + "\n");
|
|
85
296
|
return result.ok ? 0 : fail;
|
|
86
297
|
}
|
|
@@ -21,12 +21,21 @@
|
|
|
21
21
|
* is left alone.
|
|
22
22
|
* The task goes under the phase in progress when it was created.
|
|
23
23
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
24
|
+
* Phase tiles: a TaskCreate of "Phase N ..." records its id as the phase's
|
|
25
|
+
* `tasklist_id` (what :resume rebuilds the widget from), replacing the id a
|
|
26
|
+
* previous session left. The widget renders tiles in creation order, so a
|
|
27
|
+
* tile created after a higher-numbered one in the same session shows out of
|
|
28
|
+
* order (tracker-contract.md, "TaskCreate ordering"); the hook says so to the
|
|
29
|
+
* model as additionalContext. Which tiles this session created is kept in
|
|
30
|
+
* `tile-session.json` beside the tracker and starts over with a new session,
|
|
31
|
+
* so tiles a resume recreates are not compared with the old session's.
|
|
32
|
+
*
|
|
33
|
+
* Exits 0 on every path, blocks nothing, and prints only that ordering note. A
|
|
34
|
+
* failure is written to the hook failure log.
|
|
26
35
|
*/
|
|
27
36
|
|
|
28
37
|
import { spawnSync } from "node:child_process";
|
|
29
|
-
import { readFileSync } from "node:fs";
|
|
38
|
+
import { readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
30
39
|
import { dirname, join } from "node:path";
|
|
31
40
|
import { fileURLToPath } from "node:url";
|
|
32
41
|
|
|
@@ -37,6 +46,7 @@ const HOOK = "task-mirror";
|
|
|
37
46
|
const SESSION_BOUND = new Set(["session", "host-session"]);
|
|
38
47
|
const STATUSES = new Set(["pending", "in_progress", "completed"]);
|
|
39
48
|
const TILE = /^Phase \d+\b/;
|
|
49
|
+
const TILE_NO = /^Phase (\d+)\b/;
|
|
40
50
|
const MAX_INPUT = 256 * 1024;
|
|
41
51
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
42
52
|
|
|
@@ -102,6 +112,43 @@ export function mirrorArgs(payload, tracker) {
|
|
|
102
112
|
return null;
|
|
103
113
|
}
|
|
104
114
|
|
|
115
|
+
/**
|
|
116
|
+
* For a phase tile's TaskCreate: the phase it is, the id it got, and the
|
|
117
|
+
* higher-numbered phases whose tiles already exist. Null for anything else.
|
|
118
|
+
*/
|
|
119
|
+
export function tileOf(payload, tracker) {
|
|
120
|
+
if (payload?.tool_name !== "TaskCreate") return null;
|
|
121
|
+
const m = TILE_NO.exec(String(payload.tool_input?.subject ?? "").trim());
|
|
122
|
+
if (!m) return null;
|
|
123
|
+
const n = Number(m[1]);
|
|
124
|
+
const phases = Array.isArray(tracker?.phases) ? tracker.phases : [];
|
|
125
|
+
const phase = phases.find((p) => Number(p.id) === n) ?? null;
|
|
126
|
+
return {
|
|
127
|
+
phase,
|
|
128
|
+
id: createdId(payload.tool_response),
|
|
129
|
+
recorded: phase ? String(phase.meta?.tasklist_id ?? "") : "",
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* This session's tile record after creating phase `n`, and the phases whose
|
|
135
|
+
* tiles this session created before it with a higher number.
|
|
136
|
+
*/
|
|
137
|
+
export function sessionTiles(previous, sessionId, n) {
|
|
138
|
+
if (!sessionId) return { record: previous, after: [] };
|
|
139
|
+
const same =
|
|
140
|
+
previous &&
|
|
141
|
+
previous.session === sessionId &&
|
|
142
|
+
previous.tiles &&
|
|
143
|
+
typeof previous.tiles === "object";
|
|
144
|
+
const tiles = same ? { ...previous.tiles } : {};
|
|
145
|
+
const after = Object.keys(tiles)
|
|
146
|
+
.filter((p) => Number(p) > n)
|
|
147
|
+
.sort((a, b) => Number(a) - Number(b));
|
|
148
|
+
tiles[String(n)] = true;
|
|
149
|
+
return { record: { session: sessionId, tiles }, after };
|
|
150
|
+
}
|
|
151
|
+
|
|
105
152
|
export function mirror(payload, env = process.env) {
|
|
106
153
|
if (!payload || !["TaskCreate", "TaskUpdate"].includes(payload.tool_name)) return null;
|
|
107
154
|
const run = findActiveRun({
|
|
@@ -119,26 +166,70 @@ export function mirror(payload, env = process.env) {
|
|
|
119
166
|
} catch {
|
|
120
167
|
return null;
|
|
121
168
|
}
|
|
169
|
+
const tracked = (args) => {
|
|
170
|
+
const r = spawnSync("bash", [join(HERE, "phase-tracker.sh"), ...args], {
|
|
171
|
+
env: {
|
|
172
|
+
...env,
|
|
173
|
+
MULTI_AGENT_TASK_ID: run.state.taskId,
|
|
174
|
+
TRACKER_FILE: trackerPath,
|
|
175
|
+
TRACKER_QUIET: "1",
|
|
176
|
+
},
|
|
177
|
+
encoding: "utf8",
|
|
178
|
+
timeout: 8000,
|
|
179
|
+
});
|
|
180
|
+
if (r.status !== 0) logHookFailure(HOOK, `phase-tracker ${args[0]} exited ${r.status}`, env);
|
|
181
|
+
};
|
|
182
|
+
const tile = tileOf(payload, tracker);
|
|
183
|
+
if (tile) {
|
|
184
|
+
if (!tile.phase) return null;
|
|
185
|
+
if (tile.id && tile.recorded !== tile.id)
|
|
186
|
+
tracked(["meta", String(tile.phase.id), "tasklist_id", tile.id]);
|
|
187
|
+
const sidecar = join(runDirOf(run.statePath), "tile-session.json");
|
|
188
|
+
let previous;
|
|
189
|
+
try {
|
|
190
|
+
previous = JSON.parse(readFileSync(sidecar, "utf8"));
|
|
191
|
+
} catch {
|
|
192
|
+
previous = null;
|
|
193
|
+
}
|
|
194
|
+
const { record, after } = sessionTiles(
|
|
195
|
+
previous,
|
|
196
|
+
String(payload.session_id ?? ""),
|
|
197
|
+
Number(tile.phase.id),
|
|
198
|
+
);
|
|
199
|
+
if (record)
|
|
200
|
+
try {
|
|
201
|
+
const tmp = `${sidecar}.${process.pid}.tmp`;
|
|
202
|
+
writeFileSync(tmp, JSON.stringify(record));
|
|
203
|
+
renameSync(tmp, sidecar);
|
|
204
|
+
} catch (e) {
|
|
205
|
+
logHookFailure(HOOK, `tile record: ${String(e?.message || e).slice(0, 120)}`, env);
|
|
206
|
+
}
|
|
207
|
+
return after.length
|
|
208
|
+
? {
|
|
209
|
+
note:
|
|
210
|
+
`Phase ${tile.phase.id}'s tile was created after the tiles of phase ${after.join(", ")}, ` +
|
|
211
|
+
"so the task widget shows it out of order. Tiles are created in phase-number order before any " +
|
|
212
|
+
'TaskUpdate (tracker-contract.md, "TaskCreate ordering").',
|
|
213
|
+
}
|
|
214
|
+
: null;
|
|
215
|
+
}
|
|
122
216
|
const args = mirrorArgs(payload, tracker);
|
|
123
217
|
if (!args) return null;
|
|
124
|
-
|
|
125
|
-
env: {
|
|
126
|
-
...env,
|
|
127
|
-
MULTI_AGENT_TASK_ID: run.state.taskId,
|
|
128
|
-
TRACKER_FILE: trackerPath,
|
|
129
|
-
TRACKER_QUIET: "1",
|
|
130
|
-
},
|
|
131
|
-
encoding: "utf8",
|
|
132
|
-
timeout: 8000,
|
|
133
|
-
});
|
|
134
|
-
if (r.status !== 0) logHookFailure(HOOK, `phase-tracker task exited ${r.status}`, env);
|
|
218
|
+
tracked(args);
|
|
135
219
|
return args;
|
|
136
220
|
}
|
|
137
221
|
|
|
138
222
|
if (invokedDirectly(import.meta.url)) {
|
|
139
223
|
try {
|
|
140
224
|
const raw = readFileSync(0, "utf8").slice(0, MAX_INPUT);
|
|
141
|
-
mirror(JSON.parse(raw));
|
|
225
|
+
const out = mirror(JSON.parse(raw));
|
|
226
|
+
if (out?.note) {
|
|
227
|
+
process.stdout.write(
|
|
228
|
+
JSON.stringify({
|
|
229
|
+
hookSpecificOutput: { hookEventName: "PostToolUse", additionalContext: out.note },
|
|
230
|
+
}) + "\n",
|
|
231
|
+
);
|
|
232
|
+
}
|
|
142
233
|
} catch (e) {
|
|
143
234
|
try {
|
|
144
235
|
logHookFailure(HOOK, String(e?.message || e).slice(0, 200));
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
# logic is in task-mirror.mjs; this wrapper exists because every hook the
|
|
7
7
|
# pipeline registers is a `bash "$HOME/.<host>/scripts/<name>"` command.
|
|
8
8
|
#
|
|
9
|
-
# Exits 0 on every path
|
|
10
|
-
# error means no mirror, never a blocked
|
|
9
|
+
# Exits 0 on every path; its only output is a tile-ordering note for the
|
|
10
|
+
# model. A missing node or any internal error means no mirror, never a blocked
|
|
11
|
+
# tool call.
|
|
11
12
|
|
|
12
13
|
set -u
|
|
13
14
|
|
|
@@ -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());
|