@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
|
@@ -32,6 +32,8 @@ import re
|
|
|
32
32
|
import shlex
|
|
33
33
|
import time
|
|
34
34
|
|
|
35
|
+
RUN_STATE = "agent-state.json"
|
|
36
|
+
|
|
35
37
|
MAX_FILE_BYTES = 256 * 1024
|
|
36
38
|
CONFIRM_TTL_SECONDS = 600
|
|
37
39
|
|
|
@@ -47,6 +49,16 @@ AI_GENERATED = re.compile(
|
|
|
47
49
|
)
|
|
48
50
|
JIRA_TRANSITION = re.compile(r"/rest/api/(?:\d+|latest)/issue/[^/\s'\"]+/transitions\b", re.IGNORECASE)
|
|
49
51
|
POST_METHOD = re.compile(r"(?:^|\s)(?:-X|--request)\s*=?\s*['\"]?post\b", re.IGNORECASE)
|
|
52
|
+
WRITE_METHOD = re.compile(r"(?:^|\s)(?:-X|--request)\s*=?\s*['\"]?(?:post|put|patch)\b", re.IGNORECASE)
|
|
53
|
+
PUT_METHOD = re.compile(r"(?:^|\s)(?:-X|--request)\s*=?\s*['\"]?put\b", re.IGNORECASE)
|
|
54
|
+
SHARED_WRITE = re.compile(
|
|
55
|
+
r"/rest/api/(?:\d+|latest)/issue/[^/\s'\"]+/comment\b|/rest/api/content\b|hooks\.slack\.com/services/",
|
|
56
|
+
re.IGNORECASE,
|
|
57
|
+
)
|
|
58
|
+
PR_UPDATE = re.compile(
|
|
59
|
+
r"/rest/api/(?:[\d.]+|latest)/projects/[^/\s'\"]+/repos/[^/\s'\"]+/pull-requests/\d+(?=[\s'\"?]|$)"
|
|
60
|
+
)
|
|
61
|
+
DATA_FLAGS = {"-d", "--data", "--data-binary", "--data-raw", "--data-ascii"}
|
|
50
62
|
BODY_FLAG = re.compile(r"(?:^|\s)(?:-d|--data[\w-]*|--json|-F|--form)(?:\s|=|$)")
|
|
51
63
|
HEREDOC_OPEN = re.compile(r"<<(-?)\s*(['\"]?)([A-Za-z_][A-Za-z0-9_]*)\2")
|
|
52
64
|
ASSIGN = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*=")
|
|
@@ -446,6 +458,7 @@ def _gh_ask(argv, cwd):
|
|
|
446
458
|
if group == "pr" and sub in ("create", "edit"):
|
|
447
459
|
bodies = _flag_values(cleaned, {"--body"}, "-b")
|
|
448
460
|
bodies += [read_bounded(p, cwd) for p in _flag_values(cleaned, {"--body-file"}, "-F")]
|
|
461
|
+
bodies += _flag_values(cleaned, {"--title"}, "-t")
|
|
449
462
|
if any(CLOSING.search(b or "") for b in bodies):
|
|
450
463
|
return (
|
|
451
464
|
f"gh pr {sub} with a closing keyword (Closes/Fixes/Resolves #N) closes the issue on merge; "
|
|
@@ -468,10 +481,445 @@ def _jira_ask(argv, seg):
|
|
|
468
481
|
return None
|
|
469
482
|
|
|
470
483
|
|
|
484
|
+
def _shared_ask(argv, seg):
|
|
485
|
+
"""A comment, page or message posted to a system other people read."""
|
|
486
|
+
if not argv or os.path.basename(argv[0]) not in HTTP_CLIENTS:
|
|
487
|
+
return None
|
|
488
|
+
if not SHARED_WRITE.search(seg):
|
|
489
|
+
return None
|
|
490
|
+
if WRITE_METHOD.search(seg) or BODY_FLAG.search(seg):
|
|
491
|
+
return "this posts to a shared system (Jira comment, Confluence page or Slack); the user confirms each post"
|
|
492
|
+
return None
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def _payload(argv, cwd):
|
|
496
|
+
"""The request body a curl-like command sends, read from @file when given."""
|
|
497
|
+
for i, a in enumerate(argv):
|
|
498
|
+
val = None
|
|
499
|
+
if a in DATA_FLAGS and i + 1 < len(argv):
|
|
500
|
+
val = argv[i + 1]
|
|
501
|
+
else:
|
|
502
|
+
for f in DATA_FLAGS:
|
|
503
|
+
if a.startswith(f + "="):
|
|
504
|
+
val = a.split("=", 1)[1]
|
|
505
|
+
if val is not None:
|
|
506
|
+
return read_bounded(val[1:], cwd) if val.startswith("@") else val
|
|
507
|
+
return None
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def pr_update_reason(toks, seg, cwd=None):
|
|
511
|
+
"""A Bitbucket pull request PUT replaces the whole record: a body without
|
|
512
|
+
reviewers, fromRef or toRef wipes them."""
|
|
513
|
+
_, argv = command_word(toks)
|
|
514
|
+
if not argv or os.path.basename(argv[0]) not in HTTP_CLIENTS:
|
|
515
|
+
return None
|
|
516
|
+
if not PR_UPDATE.search(seg) or not PUT_METHOD.search(seg):
|
|
517
|
+
return None
|
|
518
|
+
body = _payload(argv, cwd)
|
|
519
|
+
try:
|
|
520
|
+
doc = json.loads(body) if body else None
|
|
521
|
+
except ValueError:
|
|
522
|
+
doc = None
|
|
523
|
+
if not isinstance(doc, dict):
|
|
524
|
+
return "this pull request update sends no readable JSON body; send the full record with reviewers, fromRef and toRef"
|
|
525
|
+
missing = [k for k in ("fromRef", "toRef") if not doc.get(k)]
|
|
526
|
+
if not doc.get("reviewers"):
|
|
527
|
+
missing.insert(0, "reviewers")
|
|
528
|
+
if missing:
|
|
529
|
+
return (
|
|
530
|
+
"a pull request PUT replaces the record, and this body has no "
|
|
531
|
+
+ ", ".join(missing)
|
|
532
|
+
+ "; read the current pull request and send its reviewers, fromRef, toRef and draft back"
|
|
533
|
+
)
|
|
534
|
+
return None
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
AGENT_NAME = re.compile(r"^[A-Za-z0-9._:-]{1,80}$")
|
|
538
|
+
GH_WRITE_SUBS = {
|
|
539
|
+
("pr", "create"), ("pr", "edit"), ("pr", "comment"), ("pr", "review"), ("pr", "merge"),
|
|
540
|
+
("pr", "close"), ("pr", "ready"), ("issue", "create"), ("issue", "edit"),
|
|
541
|
+
("issue", "comment"), ("issue", "close"), ("issue", "reopen"), ("release", "create"),
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
def read_only_agent(agent_type, env=None):
|
|
546
|
+
"""True when the subagent's definition denies it the Write tool: it reviews,
|
|
547
|
+
reads or reports, and anything it does outside its answer is a side effect."""
|
|
548
|
+
if not isinstance(agent_type, str) or not AGENT_NAME.match(agent_type):
|
|
549
|
+
return False
|
|
550
|
+
env = os.environ if env is None else env
|
|
551
|
+
home = env.get("HOME") or os.path.expanduser("~")
|
|
552
|
+
name = agent_type.split(":")[-1]
|
|
553
|
+
for root in (os.path.join(home, ".claude", "agents"), os.path.join(os.getcwd(), ".claude", "agents")):
|
|
554
|
+
text = read_bounded(os.path.join(root, name + ".md"), None)
|
|
555
|
+
if not text.startswith("---"):
|
|
556
|
+
continue
|
|
557
|
+
head = text.split("---", 2)[1] if text.count("---") >= 2 else ""
|
|
558
|
+
m = re.search(r"^disallowedTools:\s*(.+)$", head, re.MULTILINE)
|
|
559
|
+
return bool(m and re.search(r"\bWrite\b", m.group(1)))
|
|
560
|
+
return False
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
def subagent_side_effect(cmd):
|
|
564
|
+
"""What in this command reaches past a read-only subagent's answer, or None."""
|
|
565
|
+
stripped, _ = strip_heredoc_bodies(cmd)
|
|
566
|
+
if re.search(r"write-state\.mjs", stripped):
|
|
567
|
+
return "it writes run state (write-state.mjs)"
|
|
568
|
+
for seg in split_simple(stripped):
|
|
569
|
+
toks = tokens(seg)
|
|
570
|
+
split = _git_split(toks)
|
|
571
|
+
if split and split[2] in WRITE_SUBS:
|
|
572
|
+
return f"it runs git {split[2]}"
|
|
573
|
+
_, argv = command_word(toks)
|
|
574
|
+
if not argv:
|
|
575
|
+
continue
|
|
576
|
+
base = os.path.basename(argv[0])
|
|
577
|
+
if base in HTTP_CLIENTS and (WRITE_METHOD.search(seg) or BODY_FLAG.search(seg)):
|
|
578
|
+
return f"it sends a write request with {base}"
|
|
579
|
+
if base == "gh":
|
|
580
|
+
plains = [a for a in argv[1:] if not a.startswith("-")]
|
|
581
|
+
if len(plains) >= 2 and (GH_ALIASES.get(plains[0], plains[0]), plains[1]) in GH_WRITE_SUBS:
|
|
582
|
+
return f"it runs gh {plains[0]} {plains[1]}"
|
|
583
|
+
if plains[:1] == ["api"] and (
|
|
584
|
+
re.search(r"(?:^|\s)(?:-X|--method)\s*=?\s*(?:post|put|patch|delete)\b", seg, re.I)
|
|
585
|
+
or re.search(r"(?:^|\s)(?:-f|-F|--field|--raw-field|--input)(?:\s|=)", seg)
|
|
586
|
+
):
|
|
587
|
+
return "it writes through gh api"
|
|
588
|
+
return None
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
CLEANUP_SCRIPTS = {"purge.sh": None, "prune-logs.sh": "--yes", "run-kill.mjs": "apply"}
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
def _cleanup_ask(argv):
|
|
595
|
+
"""purge, prune-logs --yes and run-kill apply delete run history for good."""
|
|
596
|
+
if not argv:
|
|
597
|
+
return None
|
|
598
|
+
rest = argv
|
|
599
|
+
if os.path.basename(argv[0]) in ("bash", "sh", "zsh", "node"):
|
|
600
|
+
rest = argv[1:]
|
|
601
|
+
if not rest:
|
|
602
|
+
return None
|
|
603
|
+
name = os.path.basename(rest[0])
|
|
604
|
+
if name not in CLEANUP_SCRIPTS:
|
|
605
|
+
return None
|
|
606
|
+
needs = CLEANUP_SCRIPTS[name]
|
|
607
|
+
if needs and needs not in rest[1:]:
|
|
608
|
+
return None
|
|
609
|
+
return f"{name} deletes run logs or worktrees for good; the user confirms each clean-up"
|
|
610
|
+
|
|
611
|
+
|
|
471
612
|
def ask_reason(toks, seg, cwd=None):
|
|
472
613
|
"""Why a person should confirm this segment, or None."""
|
|
473
614
|
_, argv = command_word(toks)
|
|
474
|
-
return _gh_ask(argv, cwd) or _jira_ask(argv, seg)
|
|
615
|
+
return _gh_ask(argv, cwd) or _jira_ask(argv, seg) or _shared_ask(argv, seg) or _cleanup_ask(argv)
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
GH_TEXT_SUBS = {
|
|
619
|
+
("pr", "create"), ("pr", "edit"), ("pr", "comment"), ("pr", "review"),
|
|
620
|
+
("issue", "create"), ("issue", "edit"), ("issue", "comment"),
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
def gh_text(toks, cwd=None):
|
|
625
|
+
"""The title and body text a gh pull request or issue write publishes."""
|
|
626
|
+
_, argv = command_word(toks)
|
|
627
|
+
if not argv or os.path.basename(argv[0]) != "gh":
|
|
628
|
+
return ""
|
|
629
|
+
args = [a for a in argv[1:]]
|
|
630
|
+
plains = [a for a in args if not a.startswith("-")]
|
|
631
|
+
if len(plains) < 2:
|
|
632
|
+
return ""
|
|
633
|
+
if (GH_ALIASES.get(plains[0], plains[0]), plains[1]) not in GH_TEXT_SUBS:
|
|
634
|
+
return ""
|
|
635
|
+
parts = _flag_values(args, {"--body", "--title"}, "-b") + _flag_values(args, {"--title"}, "-t")
|
|
636
|
+
parts += [read_bounded(p, cwd) for p in _flag_values(args, {"--body-file"}, "-F")]
|
|
637
|
+
return "\n".join(p for p in parts if p)
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
def vercel_token_reason(toks):
|
|
641
|
+
"""A --token on the vercel command line lands in the transcript."""
|
|
642
|
+
_, argv = command_word(toks)
|
|
643
|
+
if not argv:
|
|
644
|
+
return None
|
|
645
|
+
words = [os.path.basename(argv[0])] + argv[1:3]
|
|
646
|
+
if "vercel" not in words:
|
|
647
|
+
return None
|
|
648
|
+
if any(a in ("--token", "-t") or a.startswith("--token=") for a in argv[1:]):
|
|
649
|
+
return (
|
|
650
|
+
"vercel with --token puts the token on the command line; run it through the "
|
|
651
|
+
"vercel wrapper, which reads the token from the credential store"
|
|
652
|
+
)
|
|
653
|
+
return None
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
SECRET_READ = re.compile(
|
|
657
|
+
r"credential-store\.sh['\"]?\s+get\b|\bsecurity\s+find-(?:generic|internet)-password\b[^;&|\n]*\s-w\b"
|
|
658
|
+
)
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
def secret_echo_reason(cmd):
|
|
662
|
+
"""A stored secret read with nothing capturing or consuming its output prints
|
|
663
|
+
it into the transcript."""
|
|
664
|
+
stripped, _ = strip_heredoc_bodies(cmd)
|
|
665
|
+
for m in SECRET_READ.finditer(stripped):
|
|
666
|
+
before = stripped[: m.start()]
|
|
667
|
+
if before.count("$(") > before.count(")") or before.count("`") % 2:
|
|
668
|
+
continue
|
|
669
|
+
tail = re.split(r";|&&|\|\||\n", stripped[m.end():], maxsplit=1)[0]
|
|
670
|
+
if re.search(r"(?<!\|)\|(?!\|)|>", tail):
|
|
671
|
+
continue
|
|
672
|
+
return (
|
|
673
|
+
"this prints a stored secret into the transcript; capture it in a variable "
|
|
674
|
+
"(VAR=\"$(...)\"), pipe it into the consumer, or send it to /dev/null"
|
|
675
|
+
)
|
|
676
|
+
return None
|
|
677
|
+
|
|
678
|
+
|
|
679
|
+
# --- the run this session drives ----------------------------------------------
|
|
680
|
+
|
|
681
|
+
|
|
682
|
+
def _logs_root(env=None):
|
|
683
|
+
env = os.environ if env is None else env
|
|
684
|
+
return env.get("LOGS_ROOT") or os.path.join(os.path.expanduser("~"), ".claude", "logs", "multi-agent")
|
|
685
|
+
|
|
686
|
+
|
|
687
|
+
LIVE_STATUSES = ("in_progress", "awaiting_input")
|
|
688
|
+
_DRIVING = {}
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
def driving_run(session_id, env=None):
|
|
692
|
+
"""The live run whose hostSessionId is this session, or None: status
|
|
693
|
+
in_progress or awaiting_input (the statuses _active-run.mjs treats as
|
|
694
|
+
live), the most recently written one when several match. Read once per
|
|
695
|
+
hook process.
|
|
696
|
+
|
|
697
|
+
Runs live at <root>/<project>/<task>/agent-state.json or <root>/<task>/; both
|
|
698
|
+
depths are read. A missing session id matches nothing."""
|
|
699
|
+
if not isinstance(session_id, str) or not session_id:
|
|
700
|
+
return None
|
|
701
|
+
root = _logs_root(env)
|
|
702
|
+
key = (root, session_id)
|
|
703
|
+
if key not in _DRIVING:
|
|
704
|
+
_DRIVING[key] = _scan_driving(root, session_id)
|
|
705
|
+
return _DRIVING[key]
|
|
706
|
+
|
|
707
|
+
|
|
708
|
+
def _scan_driving(root, session_id):
|
|
709
|
+
candidates = []
|
|
710
|
+
try:
|
|
711
|
+
for a in os.listdir(root):
|
|
712
|
+
pa = os.path.join(root, a)
|
|
713
|
+
if not os.path.isdir(pa):
|
|
714
|
+
continue
|
|
715
|
+
candidates.append(os.path.join(pa, RUN_STATE))
|
|
716
|
+
try:
|
|
717
|
+
for b in os.listdir(pa):
|
|
718
|
+
candidates.append(os.path.join(pa, b, RUN_STATE))
|
|
719
|
+
except OSError:
|
|
720
|
+
continue
|
|
721
|
+
except OSError:
|
|
722
|
+
return None
|
|
723
|
+
best, best_t = None, -1.0
|
|
724
|
+
for path in candidates:
|
|
725
|
+
try:
|
|
726
|
+
st = os.stat(path)
|
|
727
|
+
if st.st_size > 4 * 1024 * 1024:
|
|
728
|
+
continue
|
|
729
|
+
with open(path, encoding="utf-8") as fh:
|
|
730
|
+
doc = json.load(fh)
|
|
731
|
+
except (OSError, ValueError):
|
|
732
|
+
continue
|
|
733
|
+
if not isinstance(doc, dict) or doc.get("hostSessionId") != session_id:
|
|
734
|
+
continue
|
|
735
|
+
if doc.get("status") in LIVE_STATUSES and st.st_mtime > best_t:
|
|
736
|
+
best, best_t = doc, st.st_mtime
|
|
737
|
+
return best
|
|
738
|
+
|
|
739
|
+
|
|
740
|
+
def run_commit_reason(doc):
|
|
741
|
+
"""Why a commit in the attended run this session drives needs the user's
|
|
742
|
+
yes (rules.md, "No auto-commit"), or None for no run or an unattended one."""
|
|
743
|
+
if not isinstance(doc, dict):
|
|
744
|
+
return None
|
|
745
|
+
launch = doc.get("launchRequest") if isinstance(doc.get("launchRequest"), dict) else {}
|
|
746
|
+
if doc.get("autopilot") is True or launch.get("mode") in ("autopilot", "background"):
|
|
747
|
+
return None
|
|
748
|
+
return (
|
|
749
|
+
"this commits inside the pipeline run this session is driving (" + str(doc.get("taskId") or "?")
|
|
750
|
+
+ "); a commit waits for the user's explicit yes"
|
|
751
|
+
)
|
|
752
|
+
|
|
753
|
+
|
|
754
|
+
# --- output format inside a run --------------------------------------------------
|
|
755
|
+
|
|
756
|
+
# A repo profile may put the ticket first in any of its styles: [KEY-1], (KEY-1),
|
|
757
|
+
# KEY-1:, #1 (repo-profile-derive.mjs PREFIX_RE). The type and scope follow the
|
|
758
|
+
# same reader's CONV_RE: a lowercase word, any parenthesised scope.
|
|
759
|
+
TICKET_PREFIX = r"(\s*[\[(]?\s*(?:[A-Z][A-Z0-9]+-\d+|#\d+)\s*[\])]?:?\s+)?"
|
|
760
|
+
COMMIT_SUBJECT = re.compile(r"^" + TICKET_PREFIX + r"[a-z]+(\([^)]+\))?!?:\s+\S")
|
|
761
|
+
SUBJECT_MAX = 72
|
|
762
|
+
SKIPPED_SUBJECTS = re.compile(r"^(Merge |Revert \"|fixup! |squash! |amend! )|^\s*wip\b", re.I)
|
|
763
|
+
ENTITY = re.compile(r"&(amp|lt|gt|quot|#39|#x27);")
|
|
764
|
+
FENCE = re.compile(r"```.*?(```|$)", re.S)
|
|
765
|
+
INLINE = re.compile(r"`[^`\n]*`")
|
|
766
|
+
JIRA_CODE = re.compile(r"\{(code|noformat)[^}]*\}.*?(\{\1\}|$)", re.S)
|
|
767
|
+
# A literal backslash-n meant as a line break sits between two pieces of
|
|
768
|
+
# text: glued to the word before it and followed by more text
|
|
769
|
+
# ("Summary\nThe change", "Changes:\n- a"), or after a space before a
|
|
770
|
+
# capital ("Summary \nNext"). One ending a token (printf hello\n), quoted or
|
|
771
|
+
# bracketed is a mention of the escape; \r\n and drive paths (C:\Users\name)
|
|
772
|
+
# are removed before the search.
|
|
773
|
+
LOST_BREAK = re.compile(r"(?<=[^\s\\'\"`(\[])\\n(?=[^\s'\"`)\]])|(?<=\s)\\n(?=[A-Z])")
|
|
774
|
+
CRLF = re.compile(r"\\r\\n")
|
|
775
|
+
DRIVE_PATH = re.compile(r"\b[A-Za-z]:\\\S*")
|
|
776
|
+
|
|
777
|
+
|
|
778
|
+
def _subject(message):
|
|
779
|
+
"""The first line of a commit message that carries text. A heredoc message
|
|
780
|
+
(`-m "$(cat <<EOF ...)"`) reaches the hook with the opening quote on its
|
|
781
|
+
own line, ahead of the body."""
|
|
782
|
+
for line in (message or "").split("\n"):
|
|
783
|
+
if re.search(r"[A-Za-z0-9]", line):
|
|
784
|
+
return line.strip()
|
|
785
|
+
return ""
|
|
786
|
+
|
|
787
|
+
|
|
788
|
+
def profile_conventional(cwd):
|
|
789
|
+
"""False when the repo's stored profile says its subjects are not
|
|
790
|
+
conventional, else True (no profile, unreadable, or conventional)."""
|
|
791
|
+
script = os.path.join(os.path.dirname(os.path.abspath(__file__)), "repo-profile.mjs")
|
|
792
|
+
if not os.path.isfile(script):
|
|
793
|
+
return True
|
|
794
|
+
try:
|
|
795
|
+
import subprocess
|
|
796
|
+
|
|
797
|
+
r = subprocess.run(
|
|
798
|
+
["node", script, "resolve", "commit.format", "--repo", cwd or os.getcwd()],
|
|
799
|
+
capture_output=True, text=True, timeout=3,
|
|
800
|
+
)
|
|
801
|
+
doc = json.loads(r.stdout) if r.returncode == 0 and r.stdout.strip() else {}
|
|
802
|
+
except (OSError, ValueError, Exception):
|
|
803
|
+
return True
|
|
804
|
+
value = doc.get("value") if isinstance(doc, dict) and doc.get("use") else None
|
|
805
|
+
return not (isinstance(value, dict) and value.get("conventional") is False)
|
|
806
|
+
|
|
807
|
+
|
|
808
|
+
def commit_subject_reason(message, cwd=None):
|
|
809
|
+
"""Why a commit subject breaks the commit convention (rules.md), or None.
|
|
810
|
+
|
|
811
|
+
Not judged: a subject the hook cannot read for certain (a shell expansion
|
|
812
|
+
in it), git's merge, revert, fixup and squash subjects, checkpoint commits
|
|
813
|
+
(wip ...), and the type check in a repo whose profile is not conventional."""
|
|
814
|
+
subject = _subject(message)
|
|
815
|
+
if not subject or "$" in subject or "`" in subject or SKIPPED_SUBJECTS.search(subject):
|
|
816
|
+
return None
|
|
817
|
+
if len(subject) > SUBJECT_MAX:
|
|
818
|
+
return "the commit subject is " + str(len(subject)) + " characters; the convention caps it at " + str(SUBJECT_MAX)
|
|
819
|
+
if not COMMIT_SUBJECT.match(subject) and profile_conventional(cwd):
|
|
820
|
+
return (
|
|
821
|
+
"the commit subject does not start with a conventional type, as in type(scope): description "
|
|
822
|
+
"(after a repo profile's ticket prefix if any)"
|
|
823
|
+
)
|
|
824
|
+
return None
|
|
825
|
+
|
|
826
|
+
|
|
827
|
+
def body_text_reason(text, entities=False):
|
|
828
|
+
"""Why text published as a PR, issue or Jira body will render wrong, or
|
|
829
|
+
None: a literal backslash-n where a line break was meant, outside Markdown
|
|
830
|
+
and Jira code. With entities, an HTML entity too: a Jira payload renders
|
|
831
|
+
it verbatim, while Markdown on GitHub decodes it (and needs < for a
|
|
832
|
+
literal angle bracket)."""
|
|
833
|
+
prose = DRIVE_PATH.sub(" ", CRLF.sub(" ", INLINE.sub("", FENCE.sub("", JIRA_CODE.sub("", text or "")))))
|
|
834
|
+
if LOST_BREAK.search(prose):
|
|
835
|
+
return "the body carries a literal \\n; write real line breaks (a heredoc or a body file)"
|
|
836
|
+
m = ENTITY.search(prose) if entities else None
|
|
837
|
+
if m:
|
|
838
|
+
return "the body carries the HTML entity " + m.group(0) + "; write the character itself"
|
|
839
|
+
return None
|
|
840
|
+
|
|
841
|
+
|
|
842
|
+
def _json_strings(value, out):
|
|
843
|
+
"""The prose strings of a JSON body, leaving out ADF code blocks and text
|
|
844
|
+
marked as code."""
|
|
845
|
+
if isinstance(value, str):
|
|
846
|
+
out.append(value)
|
|
847
|
+
elif isinstance(value, dict):
|
|
848
|
+
if value.get("type") == "codeBlock":
|
|
849
|
+
return out
|
|
850
|
+
marks = value.get("marks")
|
|
851
|
+
if isinstance(marks, list) and any(isinstance(m, dict) and m.get("type") == "code" for m in marks):
|
|
852
|
+
return out
|
|
853
|
+
for v in value.values():
|
|
854
|
+
_json_strings(v, out)
|
|
855
|
+
elif isinstance(value, list):
|
|
856
|
+
for v in value:
|
|
857
|
+
_json_strings(v, out)
|
|
858
|
+
return out
|
|
859
|
+
|
|
860
|
+
|
|
861
|
+
def payload_files(argv):
|
|
862
|
+
"""Files a request sends as its body: curl -d/--data/--data-binary/--json
|
|
863
|
+
@file and --data-urlencode [name]@file, and gh api --input file and
|
|
864
|
+
-F/--field name=@file. "@-" (stdin) is the caller's to resolve."""
|
|
865
|
+
if argv and os.path.basename(argv[0]) == "gh" and len(argv) > 1 and argv[1] == "api":
|
|
866
|
+
out, i = [], 2
|
|
867
|
+
while i < len(argv):
|
|
868
|
+
a = argv[i]
|
|
869
|
+
if a == "--input" and i + 1 < len(argv):
|
|
870
|
+
out.append(argv[i + 1])
|
|
871
|
+
i += 1
|
|
872
|
+
elif a in ("-F", "--field") and i + 1 < len(argv):
|
|
873
|
+
v = argv[i + 1]
|
|
874
|
+
if "=@" in v and not v.endswith("=@-"):
|
|
875
|
+
out.append(v.split("=@", 1)[1])
|
|
876
|
+
i += 1
|
|
877
|
+
i += 1
|
|
878
|
+
return out
|
|
879
|
+
if not argv or os.path.basename(argv[0]) not in HTTP_CLIENTS:
|
|
880
|
+
return []
|
|
881
|
+
out, i = [], 1
|
|
882
|
+
while i < len(argv):
|
|
883
|
+
a = argv[i]
|
|
884
|
+
val = None
|
|
885
|
+
if a in ("-d", "--data", "--data-binary", "--data-ascii", "--json") and i + 1 < len(argv):
|
|
886
|
+
val = argv[i + 1]
|
|
887
|
+
i += 1
|
|
888
|
+
elif a.startswith(("--data=", "--data-binary=", "--data-ascii=", "--json=")):
|
|
889
|
+
val = a.split("=", 1)[1]
|
|
890
|
+
elif a == "--data-urlencode" and i + 1 < len(argv):
|
|
891
|
+
m = re.match(r"^[A-Za-z0-9_.-]*@(.+)$", argv[i + 1])
|
|
892
|
+
i += 1
|
|
893
|
+
val = "@" + m.group(1) if m else None
|
|
894
|
+
if val and val.startswith("@") and len(val) > 1 and val != "@-":
|
|
895
|
+
out.append(val[1:])
|
|
896
|
+
i += 1
|
|
897
|
+
return out
|
|
898
|
+
|
|
899
|
+
|
|
900
|
+
def stdin_payload(argv):
|
|
901
|
+
"""True when the request reads its body from stdin (-d @-, --data-binary @-, --input -)."""
|
|
902
|
+
return "@-" in argv or (len(argv) > 2 and argv[1] == "api" and "--input" in argv and "-" in argv)
|
|
903
|
+
|
|
904
|
+
|
|
905
|
+
# Jira issue and comment endpoints only: Bitbucket Server (/rest/api/1.0/)
|
|
906
|
+
# takes Markdown and Confluence storage format needs entities.
|
|
907
|
+
JIRA_API = re.compile(r"/rest/api/(?:2|3|latest)/(?:issue|comment)\b", re.I)
|
|
908
|
+
|
|
909
|
+
|
|
910
|
+
def payload_reason(path, cwd, entities=True, raw=None):
|
|
911
|
+
"""body_text_reason over a request body file (or raw text). A JSON body is
|
|
912
|
+
judged on its decoded string values, where an escaped newline is a real
|
|
913
|
+
line break. Entities are judged only for a Jira target, which renders
|
|
914
|
+
them verbatim; GitHub and Bitbucket Markdown decode them."""
|
|
915
|
+
raw = read_bounded(path, cwd) if raw is None else raw
|
|
916
|
+
if not raw:
|
|
917
|
+
return None
|
|
918
|
+
try:
|
|
919
|
+
text = "\n".join(_json_strings(json.loads(raw), []))
|
|
920
|
+
except ValueError:
|
|
921
|
+
text = raw
|
|
922
|
+
return body_text_reason(text, entities=entities)
|
|
475
923
|
|
|
476
924
|
|
|
477
925
|
# --- confirmations ---------------------------------------------------------------
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file _memory-policy.mjs - what the per-repo memory stores may keep, and
|
|
3
|
+
* how much weight an entry has earned.
|
|
4
|
+
*
|
|
5
|
+
* ledgerRowId(row) the stable id of a learnings-ledger row, the one
|
|
6
|
+
* feedback is recorded against
|
|
7
|
+
* checkedSlug(slug) the slug as one folder name; a separator, NUL, `.`
|
|
8
|
+
* or `..`, anything that could leave
|
|
9
|
+
* ~/.claude/memory/multi-agent/, throws
|
|
10
|
+
* memoryExcluded(slug) prefs.global.memory.excludeProjects lists repo slugs
|
|
11
|
+
* whose memory is never written; every store asks
|
|
12
|
+
* before it writes
|
|
13
|
+
* standing(feedback) what the outcomes recorded against one ledger entry
|
|
14
|
+
* say about it: suppressed (marked corrected, or
|
|
15
|
+
* judged a dead end more often than useful, twice or
|
|
16
|
+
* more), corroborated (useful at least twice) and a
|
|
17
|
+
* strength that decays with age (ACT-R base level,
|
|
18
|
+
* ln of the sum of age^-0.5 over the useful dates)
|
|
19
|
+
* nearDuplicate(a, b) two statements that say the same thing in other
|
|
20
|
+
* words: TF-IDF cosine at or above the threshold, the
|
|
21
|
+
* same numbers, the same negations and mostly the
|
|
22
|
+
* same word order ("A over B" is not "B over A")
|
|
23
|
+
*
|
|
24
|
+
* Zero dependencies.
|
|
25
|
+
*
|
|
26
|
+
* @module pipeline/scripts/_memory-policy
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { readFileSync } from "node:fs";
|
|
30
|
+
import { homedir } from "node:os";
|
|
31
|
+
import { join } from "node:path";
|
|
32
|
+
|
|
33
|
+
import { shortId, tokens } from "./_retrieval.mjs";
|
|
34
|
+
|
|
35
|
+
const CORROBORATION = 2;
|
|
36
|
+
const NEAR_DUP = 0.9;
|
|
37
|
+
const ORDER_OVERLAP = 0.5;
|
|
38
|
+
const NEGATIONS = /\b(not|never|no|none|without|except|only|avoid|don't|do not)\b/gi;
|
|
39
|
+
|
|
40
|
+
function prefs(env) {
|
|
41
|
+
const p =
|
|
42
|
+
env.MULTI_AGENT_PREFS || join(env.HOME || homedir(), ".claude", "multi-agent-preferences.json");
|
|
43
|
+
try {
|
|
44
|
+
return JSON.parse(readFileSync(p, "utf8"));
|
|
45
|
+
} catch {
|
|
46
|
+
return {};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Stable id of a ledger row: its kind and its whitespace-folded statement. */
|
|
51
|
+
export function ledgerRowId(row) {
|
|
52
|
+
const statement = String(row?.statement || "")
|
|
53
|
+
.toLowerCase()
|
|
54
|
+
.replace(/\s+/g, " ")
|
|
55
|
+
.trim();
|
|
56
|
+
return shortId("L", row?.kind, statement);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The slug, when it names one folder; throws on anything that could leave the
|
|
61
|
+
* memory folder (a separator, NUL, `.` or `..`). Spaces and non-ASCII letters
|
|
62
|
+
* are fine: a repository folder may carry them.
|
|
63
|
+
*/
|
|
64
|
+
export function checkedSlug(slug) {
|
|
65
|
+
const s = String(slug ?? "");
|
|
66
|
+
if (!s || s.length > 255 || /[/\\\0]/.test(s) || s === "." || s === "..") {
|
|
67
|
+
throw new Error(`not a repository slug: ${JSON.stringify(s.slice(0, 80))}`);
|
|
68
|
+
}
|
|
69
|
+
return s.normalize("NFC");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** True when the repo's memory must not be written. */
|
|
73
|
+
export function memoryExcluded(slug, env = process.env) {
|
|
74
|
+
const list = prefs(env)?.global?.memory?.excludeProjects;
|
|
75
|
+
const want = String(slug ?? "").normalize("NFC");
|
|
76
|
+
return Array.isArray(list) && list.some((p) => String(p).normalize("NFC") === want);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @param {Array<{ts: string, outcome: string}>} feedback rows for one entry
|
|
81
|
+
* @returns {{useful: number, deadEnd: number, corrected: number, suppressed: boolean, corroborated: boolean, strength: number}}
|
|
82
|
+
*/
|
|
83
|
+
export function standing(feedback, now = Date.now()) {
|
|
84
|
+
const useful = feedback.filter((f) => f.outcome === "useful");
|
|
85
|
+
const deadEnd = feedback.filter((f) => f.outcome === "dead_end").length;
|
|
86
|
+
const corrected = feedback.filter((f) => f.outcome === "corrected").length;
|
|
87
|
+
const sum = useful.reduce((s, f) => {
|
|
88
|
+
const t = Date.parse(f.ts);
|
|
89
|
+
const days = Number.isNaN(t) ? 365 : Math.max(0, (now - t) / 86400000);
|
|
90
|
+
return s + Math.pow(days + 1, -0.5);
|
|
91
|
+
}, 0);
|
|
92
|
+
return {
|
|
93
|
+
useful: useful.length,
|
|
94
|
+
deadEnd,
|
|
95
|
+
corrected,
|
|
96
|
+
suppressed: corrected > 0 || (deadEnd >= CORROBORATION && deadEnd > useful.length),
|
|
97
|
+
corroborated: useful.length >= CORROBORATION,
|
|
98
|
+
strength: sum > 0 ? Math.log(sum) + 10 : 0,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const numbersIn = (s) => (String(s).match(/\d+(?:\.\d+)?/g) || []).sort().join(",");
|
|
103
|
+
const negationsIn = (s) =>
|
|
104
|
+
(String(s).toLowerCase().match(NEGATIONS) || [])
|
|
105
|
+
.map((w) => w.replace(/\s+/g, " "))
|
|
106
|
+
.sort()
|
|
107
|
+
.join(",");
|
|
108
|
+
|
|
109
|
+
/** Inverse document frequencies over a corpus of token lists. */
|
|
110
|
+
function idf(corpus) {
|
|
111
|
+
const df = new Map();
|
|
112
|
+
for (const doc of corpus) for (const t of new Set(doc)) df.set(t, (df.get(t) || 0) + 1);
|
|
113
|
+
const n = corpus.length;
|
|
114
|
+
return (t) => Math.log(1 + n / (df.get(t) || 1));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function tfidf(doc, weight) {
|
|
118
|
+
const tf = new Map();
|
|
119
|
+
for (const t of doc) tf.set(t, (tf.get(t) || 0) + 1);
|
|
120
|
+
const v = new Map();
|
|
121
|
+
for (const [t, c] of tf) v.set(t, c * weight(t));
|
|
122
|
+
return v;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function cosine(va, vb) {
|
|
126
|
+
let dot = 0;
|
|
127
|
+
for (const [t, w] of va) dot += w * (vb.get(t) || 0);
|
|
128
|
+
const norm = (v) => Math.sqrt([...v.values()].reduce((s, w) => s + w * w, 0));
|
|
129
|
+
const d = norm(va) * norm(vb);
|
|
130
|
+
return d ? dot / d : 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Jaccard overlap of adjacent token pairs: low when the same words come in another order. */
|
|
134
|
+
function orderOverlap(a, b) {
|
|
135
|
+
const pairs = (doc) => new Set(doc.slice(1).map((t, i) => `${doc[i]} ${t}`));
|
|
136
|
+
const pa = pairs(a);
|
|
137
|
+
const pb = pairs(b);
|
|
138
|
+
if (!pa.size || !pb.size) return 0;
|
|
139
|
+
let shared = 0;
|
|
140
|
+
for (const p of pa) if (pb.has(p)) shared++;
|
|
141
|
+
return shared / (pa.size + pb.size - shared);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* The existing statement `candidate` repeats in other words, or null.
|
|
146
|
+
*
|
|
147
|
+
* @param {string} candidate
|
|
148
|
+
* @param {string[]} existing statements of the same kind
|
|
149
|
+
*/
|
|
150
|
+
export function nearDuplicate(candidate, existing) {
|
|
151
|
+
const c = tokens(candidate);
|
|
152
|
+
if (c.length < 3) return null;
|
|
153
|
+
const docs = existing.map((s) => tokens(s));
|
|
154
|
+
const weight = idf([...docs, c]);
|
|
155
|
+
const vc = tfidf(c, weight);
|
|
156
|
+
const nums = numbersIn(candidate);
|
|
157
|
+
const negs = negationsIn(candidate);
|
|
158
|
+
for (let i = 0; i < existing.length; i++) {
|
|
159
|
+
if (numbersIn(existing[i]) !== nums) continue;
|
|
160
|
+
if (negationsIn(existing[i]) !== negs) continue;
|
|
161
|
+
if (orderOverlap(c, docs[i]) < ORDER_OVERLAP) continue;
|
|
162
|
+
if (cosine(vc, tfidf(docs[i], weight)) >= NEAR_DUP) return existing[i];
|
|
163
|
+
}
|
|
164
|
+
return null;
|
|
165
|
+
}
|