@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
|
@@ -14,7 +14,10 @@
|
|
|
14
14
|
* run re-discovers the same structure and reviewers re-flag preferences the team
|
|
15
15
|
* already rejected.
|
|
16
16
|
*
|
|
17
|
-
* Each line conforms to pipeline/schemas/learnings-ledger.schema.json.
|
|
17
|
+
* Each line conforms to pipeline/schemas/learnings-ledger.schema.json. Every
|
|
18
|
+
* entry passes the secret scan in _secret-scan.mjs before it is written, and
|
|
19
|
+
* `<private>` spans are removed first; a refused entry is counted in
|
|
20
|
+
* `droppedBySecretScan`.
|
|
18
21
|
*
|
|
19
22
|
* Subcommands:
|
|
20
23
|
* add --kind <fact|convention|rejected-preference> --statement "..."
|
|
@@ -43,6 +46,12 @@
|
|
|
43
46
|
* severity rejections are NOT distilled - a wrong rejection of a
|
|
44
47
|
* blocking issue must never permanently suppress that class. Exit 2
|
|
45
48
|
* when nothing new was distilled (a repeat of the same triage).
|
|
49
|
+
* feedback --id <id> --outcome <useful|dead_end|corrected> [--task <id>] [--repo-slug <slug>]
|
|
50
|
+
* Record how an injected entry fared. An entry marked corrected,
|
|
51
|
+
* or judged a dead end at least twice and more often than useful,
|
|
52
|
+
* leaves brief and profile; one useful at least twice ranks by
|
|
53
|
+
* its strength too (_memory-policy.mjs). Kept beside the ledger
|
|
54
|
+
* in learnings-feedback.jsonl.
|
|
46
55
|
* forget [--statement "..."] [--kind <kind>] [--repo-slug <slug>]
|
|
47
56
|
* Remove ledger entries matching the statement substring and/or
|
|
48
57
|
* kind (rewrites the file). Use to clear a bad/stale learning.
|
|
@@ -70,7 +79,15 @@ import {
|
|
|
70
79
|
import { homedir } from "node:os";
|
|
71
80
|
import { execFileSync } from "node:child_process";
|
|
72
81
|
import { join, basename, dirname } from "node:path";
|
|
73
|
-
import { bm25, recencyRank, rrf, tokens,
|
|
82
|
+
import { bm25, recencyRank, rrf, tokens, recallConfig } from "./_retrieval.mjs";
|
|
83
|
+
import {
|
|
84
|
+
checkedSlug,
|
|
85
|
+
ledgerRowId,
|
|
86
|
+
memoryExcluded,
|
|
87
|
+
nearDuplicate,
|
|
88
|
+
standing,
|
|
89
|
+
} from "./_memory-policy.mjs";
|
|
90
|
+
import { openScanner, stripPrivate } from "./_secret-scan.mjs";
|
|
74
91
|
|
|
75
92
|
const argv = process.argv.slice(2);
|
|
76
93
|
const SUB = argv[0];
|
|
@@ -110,6 +127,10 @@ function die(msg, code = 1) {
|
|
|
110
127
|
// the learning replay always miss. `--git-common-dir` points at the main
|
|
111
128
|
// repository's .git for every linked worktree.
|
|
112
129
|
function repoSlug() {
|
|
130
|
+
return checkedSlug(rawRepoSlug());
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function rawRepoSlug() {
|
|
113
134
|
if (flags["repo-slug"]) return String(flags["repo-slug"]);
|
|
114
135
|
if (process.env.MULTI_AGENT_REPO_SLUG) return process.env.MULTI_AGENT_REPO_SLUG;
|
|
115
136
|
try {
|
|
@@ -150,6 +171,36 @@ function ledgerPath(slug) {
|
|
|
150
171
|
);
|
|
151
172
|
}
|
|
152
173
|
|
|
174
|
+
function feedbackPath(slug) {
|
|
175
|
+
return join(dirname(ledgerPath(slug)), "learnings-feedback.jsonl");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** id -> outcome rows recorded against it. */
|
|
179
|
+
function readFeedback(slug) {
|
|
180
|
+
const p = feedbackPath(slug);
|
|
181
|
+
const byId = new Map();
|
|
182
|
+
if (!existsSync(p)) return byId;
|
|
183
|
+
for (const line of readFileSync(p, "utf8").split("\n")) {
|
|
184
|
+
if (!line.trim()) continue;
|
|
185
|
+
try {
|
|
186
|
+
const f = JSON.parse(line);
|
|
187
|
+
if (!byId.has(f.id)) byId.set(f.id, []);
|
|
188
|
+
byId.get(f.id).push(f);
|
|
189
|
+
} catch {
|
|
190
|
+
/* skip corrupted line */
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return byId;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Rows with their standing; suppressed ones dropped. */
|
|
197
|
+
function standingRows(slug, rows) {
|
|
198
|
+
const fb = readFeedback(slug);
|
|
199
|
+
return rows
|
|
200
|
+
.map((r) => ({ r, s: standing(fb.get(rowId(r)) ?? []) }))
|
|
201
|
+
.filter((x) => !x.s.suppressed);
|
|
202
|
+
}
|
|
203
|
+
|
|
153
204
|
function ensureDir(p) {
|
|
154
205
|
mkdirSync(dirname(p), { recursive: true });
|
|
155
206
|
}
|
|
@@ -171,9 +222,7 @@ function normStatement(s) {
|
|
|
171
222
|
// Stable pointer for a ledger row, from the same fields the dedup key uses.
|
|
172
223
|
// Not stored on the row: an existing ledger gets ids without a migration, and a
|
|
173
224
|
// re-added entry keeps the id it already had.
|
|
174
|
-
|
|
175
|
-
return shortId("L", row.kind, normStatement(row.statement));
|
|
176
|
-
}
|
|
225
|
+
const rowId = ledgerRowId;
|
|
177
226
|
|
|
178
227
|
function readLedger(slug) {
|
|
179
228
|
const p = ledgerPath(slug);
|
|
@@ -197,8 +246,22 @@ function writeRow(slug, row) {
|
|
|
197
246
|
appendFileSync(p, JSON.stringify(row) + "\n", "utf8");
|
|
198
247
|
}
|
|
199
248
|
|
|
249
|
+
// One scanner per process; every write goes through it (_secret-scan.mjs).
|
|
250
|
+
let scanner = null;
|
|
251
|
+
const secretScan = { dropped: 0 };
|
|
252
|
+
function passesSecretScan(text) {
|
|
253
|
+
if (!scanner) {
|
|
254
|
+
scanner = openScanner();
|
|
255
|
+
process.on("exit", () => scanner.close());
|
|
256
|
+
}
|
|
257
|
+
if (scanner.isClean(text)) return true;
|
|
258
|
+
secretScan.dropped++;
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
|
|
200
262
|
/** Append one entry unless an entry with the same kind + normalized statement
|
|
201
|
-
* already exists (idempotent)
|
|
263
|
+
* already exists (idempotent), or the text fails the secret scan. Returns
|
|
264
|
+
* true when written. */
|
|
202
265
|
function addEntry(slug, { kind, statement, scope, task, confidence, diagnosis, source }, existing) {
|
|
203
266
|
// One line, enforced here rather than hoped for. The schema says "the
|
|
204
267
|
// knowledge itself, in one line", and both blocks render one entry per line -
|
|
@@ -206,16 +269,23 @@ function addEntry(slug, { kind, statement, scope, task, confidence, diagnosis, s
|
|
|
206
269
|
// `from-triage` builds statements out of model-written finding text, a
|
|
207
270
|
// newline plus a plausible-looking `</repo-profile>` would break out of the
|
|
208
271
|
// injected block itself. Collapsing whitespace closes both at the store.
|
|
209
|
-
const stmt = oneLine(statement);
|
|
272
|
+
const stmt = oneLine(stripPrivate(statement));
|
|
210
273
|
if (!stmt) return false;
|
|
211
274
|
const seenKey = `${kind}::${normStatement(stmt)}`;
|
|
212
275
|
if (existing.has(seenKey)) return false;
|
|
276
|
+
const sameKind = [...existing]
|
|
277
|
+
.filter((k) => k.startsWith(`${kind}::`))
|
|
278
|
+
.map((k) => k.slice(kind.length + 2));
|
|
279
|
+
if (nearDuplicate(stmt.toLowerCase(), sameKind)) return false;
|
|
280
|
+
const diag = oneLine(stripPrivate(diagnosis));
|
|
281
|
+
const scopeText = scope || "global";
|
|
282
|
+
if (!passesSecretScan(`${stmt}\n${diag}\n${scopeText}`)) return false;
|
|
213
283
|
const row = {
|
|
214
284
|
v: "1.0.0",
|
|
215
285
|
ts: new Date().toISOString(),
|
|
216
286
|
repo_slug: slug,
|
|
217
287
|
kind,
|
|
218
|
-
scope:
|
|
288
|
+
scope: scopeText,
|
|
219
289
|
statement: stmt,
|
|
220
290
|
source_task: task || null,
|
|
221
291
|
confidence: CONFIDENCES.has(confidence) ? confidence : "medium",
|
|
@@ -223,7 +293,6 @@ function addEntry(slug, { kind, statement, scope, task, confidence, diagnosis, s
|
|
|
223
293
|
// Optional causal diagnosis (Reflexion P5): the "why it failed" behind a
|
|
224
294
|
// lesson. Verbal diagnosis is what turns a detected error into a prevented
|
|
225
295
|
// one; a bare outcome does not. Stored only when supplied.
|
|
226
|
-
const diag = oneLine(diagnosis);
|
|
227
296
|
if (diag) row.diagnosis = diag;
|
|
228
297
|
// Who found it. Optional so old rows stay valid, but written whenever the
|
|
229
298
|
// caller knows: a machine-mined path correction and a model's architectural
|
|
@@ -235,11 +304,19 @@ function addEntry(slug, { kind, statement, scope, task, confidence, diagnosis, s
|
|
|
235
304
|
return true;
|
|
236
305
|
}
|
|
237
306
|
|
|
307
|
+
function refuseExcluded(slug) {
|
|
308
|
+
if (!memoryExcluded(slug)) return false;
|
|
309
|
+
process.stdout.write(JSON.stringify({ ok: true, slug, added: 0, excluded: true }) + "\n");
|
|
310
|
+
process.exitCode = 2;
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
|
|
238
314
|
function cmdAdd() {
|
|
239
315
|
const kind = flags.kind;
|
|
240
316
|
if (!KINDS.has(kind)) die(`--kind must be one of: ${[...KINDS].join(", ")}`, 1);
|
|
241
317
|
if (!flags.statement || flags.statement === true) die('add needs --statement "..."', 1);
|
|
242
318
|
const slug = repoSlug();
|
|
319
|
+
if (refuseExcluded(slug)) return;
|
|
243
320
|
const existing = new Set(readLedger(slug).map((r) => `${r.kind}::${normStatement(r.statement)}`));
|
|
244
321
|
const wrote = addEntry(
|
|
245
322
|
slug,
|
|
@@ -256,7 +333,13 @@ function cmdAdd() {
|
|
|
256
333
|
existing,
|
|
257
334
|
);
|
|
258
335
|
process.stdout.write(
|
|
259
|
-
JSON.stringify({
|
|
336
|
+
JSON.stringify({
|
|
337
|
+
ok: true,
|
|
338
|
+
slug,
|
|
339
|
+
added: wrote ? 1 : 0,
|
|
340
|
+
deduped: wrote || secretScan.dropped ? 0 : 1,
|
|
341
|
+
droppedBySecretScan: secretScan.dropped,
|
|
342
|
+
}) + "\n",
|
|
260
343
|
);
|
|
261
344
|
process.exit(wrote ? 0 : 2);
|
|
262
345
|
}
|
|
@@ -267,6 +350,7 @@ function cmdFromTriage() {
|
|
|
267
350
|
if (!existsSync(triagePath)) die(`triage file not found: ${triagePath}`, 1);
|
|
268
351
|
const task = flags.task && flags.task !== true ? String(flags.task) : null;
|
|
269
352
|
const slug = repoSlug();
|
|
353
|
+
if (refuseExcluded(slug)) return;
|
|
270
354
|
let triage;
|
|
271
355
|
try {
|
|
272
356
|
triage = JSON.parse(readFileSync(triagePath, "utf8"));
|
|
@@ -311,7 +395,15 @@ function cmdFromTriage() {
|
|
|
311
395
|
written++;
|
|
312
396
|
}
|
|
313
397
|
}
|
|
314
|
-
process.stdout.write(
|
|
398
|
+
process.stdout.write(
|
|
399
|
+
JSON.stringify({
|
|
400
|
+
ok: true,
|
|
401
|
+
slug,
|
|
402
|
+
added: written,
|
|
403
|
+
skippedBlocking,
|
|
404
|
+
droppedBySecretScan: secretScan.dropped,
|
|
405
|
+
}) + "\n",
|
|
406
|
+
);
|
|
315
407
|
process.exitCode = written ? 0 : 2;
|
|
316
408
|
return;
|
|
317
409
|
}
|
|
@@ -430,12 +522,14 @@ function compareCodepoints(a, b) {
|
|
|
430
522
|
}
|
|
431
523
|
|
|
432
524
|
function cmdBrief() {
|
|
433
|
-
const slug =
|
|
525
|
+
const slug = repoSlug();
|
|
434
526
|
const cfg = recallConfig();
|
|
435
527
|
const max = flags.max ? Math.max(1, Number(flags.max)) : 20;
|
|
436
528
|
const task = flags.task && flags.task !== true ? String(flags.task) : null;
|
|
437
529
|
const scope = flags.scope && flags.scope !== true ? String(flags.scope) : null;
|
|
438
|
-
const
|
|
530
|
+
const scored = standingRows(slug, readLedger(slug));
|
|
531
|
+
const rows = scored.map((x) => x.r);
|
|
532
|
+
const strengthOf = new Map(scored.map((x) => [x.r, x.s.corroborated ? x.s.strength : 0]));
|
|
439
533
|
if (rows.length === 0) {
|
|
440
534
|
process.exit(2); // nothing to inject; caller skips silently
|
|
441
535
|
}
|
|
@@ -471,8 +565,13 @@ function cmdBrief() {
|
|
|
471
565
|
);
|
|
472
566
|
const fresh = recencyRank(pool, { halfLifeDays: cfg.halfLifeDays });
|
|
473
567
|
const matched = new Set(relevance.filter((x) => x.score > 0).map((x) => x.index));
|
|
474
|
-
|
|
475
|
-
|
|
568
|
+
// Entries recorded useful at least twice rank by their strength as well;
|
|
569
|
+
// with no feedback this list is empty and the fusion is unchanged.
|
|
570
|
+
const earned = pool
|
|
571
|
+
.map((r, index) => ({ index, score: strengthOf.get(r) || 0 }))
|
|
572
|
+
.sort((a, b) => b.score - a.score);
|
|
573
|
+
const fused = rrf([relevance, fresh, earned], { k: [10, 60, 60], weights: [3, 1, 1] }).filter(
|
|
574
|
+
(x) => matched.has(x.index),
|
|
476
575
|
);
|
|
477
576
|
ordered = fused.slice(0, max).map((x) => pool[x.index]);
|
|
478
577
|
// A task whose words match nothing in the ledger must not silently inject an
|
|
@@ -527,10 +626,10 @@ function scopeMatches(entryScope, target) {
|
|
|
527
626
|
// everything after it. Detail is deliberately absent; each line carries the
|
|
528
627
|
// pointer that buys it back.
|
|
529
628
|
function cmdProfile() {
|
|
530
|
-
const slug =
|
|
629
|
+
const slug = repoSlug();
|
|
531
630
|
const cfg = recallConfig();
|
|
532
631
|
const max = flags.max ? Math.max(1, Number(flags.max)) : 60;
|
|
533
|
-
const rows = readLedger(slug);
|
|
632
|
+
const rows = standingRows(slug, readLedger(slug)).map((x) => x.r);
|
|
534
633
|
if (rows.length === 0) process.exit(2);
|
|
535
634
|
|
|
536
635
|
const ordered = rows
|
|
@@ -565,7 +664,7 @@ function cmdProfile() {
|
|
|
565
664
|
function cmdShow() {
|
|
566
665
|
const id = flags.id && flags.id !== true ? String(flags.id) : null;
|
|
567
666
|
if (!id) die("show needs --id <id>", 1);
|
|
568
|
-
const slug =
|
|
667
|
+
const slug = repoSlug();
|
|
569
668
|
const want = id.startsWith("L:") ? id : `L:${id}`;
|
|
570
669
|
const row = readLedger(slug).find((r) => rowId(r) === want);
|
|
571
670
|
if (!row) {
|
|
@@ -592,7 +691,7 @@ function cmdShow() {
|
|
|
592
691
|
function cmdTimeline() {
|
|
593
692
|
const id = flags.anchor && flags.anchor !== true ? String(flags.anchor) : null;
|
|
594
693
|
if (!id) die("timeline needs --anchor <id>", 1);
|
|
595
|
-
const slug =
|
|
694
|
+
const slug = repoSlug();
|
|
596
695
|
const before = flags.before && flags.before !== true ? Math.max(0, Number(flags.before)) : 3;
|
|
597
696
|
const after = flags.after && flags.after !== true ? Math.max(0, Number(flags.after)) : 3;
|
|
598
697
|
const want = id.startsWith("L:") ? id : `L:${id}`;
|
|
@@ -622,15 +721,32 @@ function cmdTimeline() {
|
|
|
622
721
|
}
|
|
623
722
|
|
|
624
723
|
function cmdPath() {
|
|
625
|
-
process.stdout.write(
|
|
626
|
-
ledgerPath(flags["repo-slug"] ? String(flags["repo-slug"]) : repoSlug()) + "\n",
|
|
627
|
-
);
|
|
724
|
+
process.stdout.write(ledgerPath(repoSlug()) + "\n");
|
|
628
725
|
process.exitCode = 0;
|
|
629
726
|
return;
|
|
630
727
|
}
|
|
631
728
|
|
|
729
|
+
const OUTCOMES = new Set(["useful", "dead_end", "corrected"]);
|
|
730
|
+
|
|
731
|
+
function cmdFeedback() {
|
|
732
|
+
const slug = repoSlug();
|
|
733
|
+
const raw = flags.id && flags.id !== true ? String(flags.id).replace(/^\[|\]$/g, "") : "";
|
|
734
|
+
const id = raw && !raw.startsWith("L:") ? `L:${raw}` : raw;
|
|
735
|
+
const outcome = String(flags.outcome || "");
|
|
736
|
+
if (!id || !OUTCOMES.has(outcome))
|
|
737
|
+
die("feedback needs --id <id> --outcome <useful|dead_end|corrected>", 1);
|
|
738
|
+
if (!readLedger(slug).some((r) => rowId(r) === id)) die(`no ledger entry ${id}`, 2);
|
|
739
|
+
if (refuseExcluded(slug)) return;
|
|
740
|
+
const row = { ts: new Date().toISOString(), id, outcome };
|
|
741
|
+
if (flags.task && flags.task !== true) row.task = String(flags.task);
|
|
742
|
+
ensureDir(feedbackPath(slug));
|
|
743
|
+
appendFileSync(feedbackPath(slug), JSON.stringify(row) + "\n", "utf8");
|
|
744
|
+
const s = standing(readFeedback(slug).get(id) ?? []);
|
|
745
|
+
process.stdout.write(JSON.stringify({ ok: true, slug, id, ...s }) + "\n");
|
|
746
|
+
}
|
|
747
|
+
|
|
632
748
|
function cmdStats() {
|
|
633
|
-
const slug =
|
|
749
|
+
const slug = repoSlug();
|
|
634
750
|
const rows = readLedger(slug);
|
|
635
751
|
const counts = { fact: 0, convention: 0, "rejected-preference": 0 };
|
|
636
752
|
for (const r of rows) if (counts[r.kind] !== undefined) counts[r.kind]++;
|
|
@@ -651,6 +767,9 @@ switch (SUB) {
|
|
|
651
767
|
case "forget":
|
|
652
768
|
cmdForget();
|
|
653
769
|
break;
|
|
770
|
+
case "feedback":
|
|
771
|
+
cmdFeedback();
|
|
772
|
+
break;
|
|
654
773
|
case "brief":
|
|
655
774
|
cmdBrief();
|
|
656
775
|
break;
|
|
@@ -671,7 +790,7 @@ switch (SUB) {
|
|
|
671
790
|
break;
|
|
672
791
|
default:
|
|
673
792
|
process.stderr.write(
|
|
674
|
-
"usage: learnings-ledger.mjs <add|from-triage|forget|brief|profile|show|timeline|path|stats> [flags]\n",
|
|
793
|
+
"usage: learnings-ledger.mjs <add|from-triage|feedback|forget|brief|profile|show|timeline|path|stats> [flags]\n",
|
|
675
794
|
);
|
|
676
795
|
process.exit(64);
|
|
677
796
|
}
|
|
@@ -9,9 +9,11 @@
|
|
|
9
9
|
* pipeline/office/index.html: the list of sprite names in pipeline/office/assets
|
|
10
10
|
* (served by contract-server.mjs at /office/assets/), the strings as a JSON
|
|
11
11
|
* literal, the two code files in order inside one IIFE. It also renders the
|
|
12
|
-
* pixel grids in pipeline/office-art into PNG sprites in pipeline/office/assets
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* pixel grids in pipeline/office-art into PNG sprites in pipeline/office/assets,
|
|
13
|
+
* recolors the character sheets into the company's employees
|
|
14
|
+
* (office-art/employees.json) and writes pipeline/office/toolkit.json, the
|
|
15
|
+
* toolkit version shipped with the page; --check holds all of them to their
|
|
16
|
+
* sources.
|
|
15
17
|
*
|
|
16
18
|
* node pipeline/scripts/office-build.mjs write the page
|
|
17
19
|
* node pipeline/scripts/office-build.mjs --check exit 1 when the page on
|
|
@@ -21,7 +23,7 @@
|
|
|
21
23
|
|
|
22
24
|
import { createHash } from "node:crypto";
|
|
23
25
|
import { readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
24
|
-
import { deflateSync } from "node:zlib";
|
|
26
|
+
import { deflateSync, inflateSync } from "node:zlib";
|
|
25
27
|
import { basename, join } from "node:path";
|
|
26
28
|
import { pathToFileURL } from "node:url";
|
|
27
29
|
|
|
@@ -78,6 +80,272 @@ export function encodePng(width, height, rgba) {
|
|
|
78
80
|
]);
|
|
79
81
|
}
|
|
80
82
|
|
|
83
|
+
/**
|
|
84
|
+
* An 8-bit RGBA PNG decoded to {width, height, rgba}. Only that colour type is
|
|
85
|
+
* read (every sheet in office/assets is one); anything else throws.
|
|
86
|
+
*/
|
|
87
|
+
export function decodePng(buf) {
|
|
88
|
+
if (buf.subarray(0, 8).toString("hex") !== PNG_SIGNATURE) throw new Error("not a PNG file");
|
|
89
|
+
let o = 8;
|
|
90
|
+
let width = 0;
|
|
91
|
+
let height = 0;
|
|
92
|
+
const idat = [];
|
|
93
|
+
while (o + 8 <= buf.length) {
|
|
94
|
+
const len = buf.readUInt32BE(o);
|
|
95
|
+
const type = buf.toString("ascii", o + 4, o + 8);
|
|
96
|
+
const data = buf.subarray(o + 8, o + 8 + len);
|
|
97
|
+
if (type === "IHDR") {
|
|
98
|
+
width = data.readUInt32BE(0);
|
|
99
|
+
height = data.readUInt32BE(4);
|
|
100
|
+
if (data[8] !== 8 || data[9] !== 6 || data[12] !== 0)
|
|
101
|
+
throw new Error("only 8-bit RGBA, non-interlaced PNGs are read");
|
|
102
|
+
} else if (type === "IDAT") idat.push(data);
|
|
103
|
+
else if (type === "IEND") break;
|
|
104
|
+
o += 12 + len;
|
|
105
|
+
}
|
|
106
|
+
if (!width || !height) throw new Error("PNG has no IHDR");
|
|
107
|
+
const raw = inflateSync(Buffer.concat(idat));
|
|
108
|
+
const stride = width * 4;
|
|
109
|
+
if (raw.length !== (stride + 1) * height) throw new Error("PNG data has the wrong length");
|
|
110
|
+
const rgba = Buffer.alloc(stride * height);
|
|
111
|
+
for (let y = 0; y < height; y++) {
|
|
112
|
+
const filter = raw[y * (stride + 1)];
|
|
113
|
+
const line = y * (stride + 1) + 1;
|
|
114
|
+
for (let x = 0; x < stride; x++) {
|
|
115
|
+
const a = x >= 4 ? rgba[y * stride + x - 4] : 0;
|
|
116
|
+
const b = y > 0 ? rgba[(y - 1) * stride + x] : 0;
|
|
117
|
+
const c = x >= 4 && y > 0 ? rgba[(y - 1) * stride + x - 4] : 0;
|
|
118
|
+
let v = raw[line + x];
|
|
119
|
+
if (filter === 1) v += a;
|
|
120
|
+
else if (filter === 2) v += b;
|
|
121
|
+
else if (filter === 3) v += (a + b) >> 1;
|
|
122
|
+
else if (filter === 4) {
|
|
123
|
+
const p = a + b - c;
|
|
124
|
+
const pa = Math.abs(p - a);
|
|
125
|
+
const pb = Math.abs(p - b);
|
|
126
|
+
const pc = Math.abs(p - c);
|
|
127
|
+
v += pa <= pb && pa <= pc ? a : pb <= pc ? b : c;
|
|
128
|
+
} else if (filter !== 0) throw new Error(`unknown PNG filter ${filter}`);
|
|
129
|
+
rgba[y * stride + x] = v & 255;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return { width, height, rgba };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// --- the employees -----------------------------------------------------------
|
|
136
|
+
// office-art/employees.json gives each person of the company a base character
|
|
137
|
+
// sheet and four colours. A sheet's colours are sorted into parts by where
|
|
138
|
+
// they sit in the 16x32 frame (hair above the face, the shirt below it, then
|
|
139
|
+
// trousers and shoes) and skin by the face's tone; the near-black outline,
|
|
140
|
+
// white, the eyes and the shoes keep their colours. Each part moves to the
|
|
141
|
+
// person's colour with its shading kept.
|
|
142
|
+
|
|
143
|
+
const FRAME_H = 32;
|
|
144
|
+
const BANDS = [
|
|
145
|
+
["hair", 0, 13],
|
|
146
|
+
["face", 13, 18],
|
|
147
|
+
["top", 18, 25],
|
|
148
|
+
["bottom", 25, 28],
|
|
149
|
+
["shoes", 28, 32],
|
|
150
|
+
];
|
|
151
|
+
const PARTS = ["hair", "skin", "top", "bottom"];
|
|
152
|
+
const SKIN_NEAR = 48;
|
|
153
|
+
|
|
154
|
+
const channels = (hex) => [0, 2, 4].map((i) => parseInt(hex.slice(i, i + 2), 16));
|
|
155
|
+
|
|
156
|
+
function hsl(r, g, b) {
|
|
157
|
+
const [R, G, B] = [r / 255, g / 255, b / 255];
|
|
158
|
+
const max = Math.max(R, G, B);
|
|
159
|
+
const min = Math.min(R, G, B);
|
|
160
|
+
const l = (max + min) / 2;
|
|
161
|
+
const d = max - min;
|
|
162
|
+
if (!d) return [0, 0, l];
|
|
163
|
+
const s = d / (1 - Math.abs(2 * l - 1));
|
|
164
|
+
const h = max === R ? ((G - B) / d + 6) % 6 : max === G ? (B - R) / d + 2 : (R - G) / d + 4;
|
|
165
|
+
return [h * 60, s, l];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function rgbOf(h, s, l) {
|
|
169
|
+
const c = (1 - Math.abs(2 * l - 1)) * s;
|
|
170
|
+
const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
|
|
171
|
+
const m = l - c / 2;
|
|
172
|
+
const sector = [
|
|
173
|
+
[c, x, 0],
|
|
174
|
+
[x, c, 0],
|
|
175
|
+
[0, c, x],
|
|
176
|
+
[0, x, c],
|
|
177
|
+
[x, 0, c],
|
|
178
|
+
[c, 0, x],
|
|
179
|
+
][Math.min(5, Math.floor(h / 60))];
|
|
180
|
+
return sector.map((v) => Math.round(Math.min(1, Math.max(0, v + m)) * 255));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** The band of a sheet row: hair, face, top, bottom or shoes, by its place in the 32-row frame. */
|
|
184
|
+
export function bandOf(y) {
|
|
185
|
+
return BANDS.find(([, a, b]) => y % FRAME_H >= a && y % FRAME_H < b)[0];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* The middle of the face where it faces the viewer: the first row of frames
|
|
190
|
+
* (the front view; the second shows the back of the head), the face rows,
|
|
191
|
+
* away from the sides long hair covers and the collar rises into.
|
|
192
|
+
*/
|
|
193
|
+
export function inFaceCentre(x, y) {
|
|
194
|
+
return y >= 13 && y < 18 && x % 16 >= 5 && x % 16 <= 10;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* The part of every pixel of a sheet: hair, skin, top, bottom, keep, or null
|
|
199
|
+
* for a transparent one. A pixel is judged by its colour and its row:
|
|
200
|
+
* - near-black outline and white keep their colour everywhere;
|
|
201
|
+
* - the face's tone is skin wherever it is (neck, arms, hands, legs);
|
|
202
|
+
* - the hair rows are hair, the dark strand lines inside it included;
|
|
203
|
+
* - the face rows are skin, hair (a colour of the hair rows: long hair at the
|
|
204
|
+
* sides) or kept (eyes, mouth, the face's outline), never clothing;
|
|
205
|
+
* - the body rows are the top, except a hair colour (a braid over the
|
|
206
|
+
* shoulder), the leg rows are the bottom, and the shoes keep theirs.
|
|
207
|
+
*/
|
|
208
|
+
export function pixelParts(sheet) {
|
|
209
|
+
const { width, height, rgba } = sheet;
|
|
210
|
+
const seen = new Map();
|
|
211
|
+
for (let y = 0; y < height; y++) {
|
|
212
|
+
const band = bandOf(y);
|
|
213
|
+
for (let x = 0; x < width; x++) {
|
|
214
|
+
const o = (y * width + x) * 4;
|
|
215
|
+
if (!rgba[o + 3]) continue;
|
|
216
|
+
const hex = rgba.subarray(o, o + 3).toString("hex");
|
|
217
|
+
const counts = seen.get(hex) || {
|
|
218
|
+
hair: 0,
|
|
219
|
+
face: 0,
|
|
220
|
+
top: 0,
|
|
221
|
+
bottom: 0,
|
|
222
|
+
shoes: 0,
|
|
223
|
+
centre: 0,
|
|
224
|
+
};
|
|
225
|
+
counts[band]++;
|
|
226
|
+
if (inFaceCentre(x, y)) counts.centre++;
|
|
227
|
+
seen.set(hex, counts);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const info = new Map();
|
|
231
|
+
for (const [hex, counts] of seen) {
|
|
232
|
+
const rgb = channels(hex);
|
|
233
|
+
const [h, s, l] = hsl(...rgb);
|
|
234
|
+
const n = counts.hair + counts.face + counts.top + counts.bottom + counts.shoes;
|
|
235
|
+
const [band] = BANDS.map(([name]) => [name, counts[name]]).sort((p, q) => q[1] - p[1])[0];
|
|
236
|
+
info.set(hex, {
|
|
237
|
+
rgb,
|
|
238
|
+
counts,
|
|
239
|
+
n,
|
|
240
|
+
band,
|
|
241
|
+
fixed: Math.max(...rgb) < 0x18 || Math.min(...rgb) > 0xf8,
|
|
242
|
+
skinLike: h >= 8 && h <= 45 && s >= 0.25 && l >= 0.15,
|
|
243
|
+
h,
|
|
244
|
+
s,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
// The face is the skin-like colours of its middle, where no hair hangs, that
|
|
248
|
+
// the hair rows use less (a forehead under the fringe counts). Skin elsewhere
|
|
249
|
+
// is a colour the hair rows never use that sits next to a face colour (the
|
|
250
|
+
// shading of arms and hands); a hair highlight of a skin-like hue lives in the
|
|
251
|
+
// hair rows, and a clothing colour of a skin-like hue is far from the face or
|
|
252
|
+
// lives below the waist. Hair never reaches the middle of the face.
|
|
253
|
+
const faceTones = [...info.values()].filter(
|
|
254
|
+
(c) => c.skinLike && c.counts.centre > 0 && c.counts.centre * 2 >= c.counts.hair,
|
|
255
|
+
);
|
|
256
|
+
const near = (c, f) =>
|
|
257
|
+
Math.abs(c.s - f.s) <= 0.3 && c.rgb.every((v, i) => Math.abs(v - f.rgb[i]) <= SKIN_NEAR);
|
|
258
|
+
const skin = (c) =>
|
|
259
|
+
faceTones.includes(c) ||
|
|
260
|
+
(c.skinLike && c.counts.hair === 0 && c.band !== "bottom" && faceTones.some((f) => near(c, f)));
|
|
261
|
+
const hairColour = (c) => c.counts.hair > 0 && c.counts.hair * 2 >= c.n - c.counts.hair;
|
|
262
|
+
const out = new Array(width * height).fill(null);
|
|
263
|
+
for (let y = 0; y < height; y++) {
|
|
264
|
+
const band = bandOf(y);
|
|
265
|
+
for (let x = 0; x < width; x++) {
|
|
266
|
+
const o = (y * width + x) * 4;
|
|
267
|
+
if (!rgba[o + 3]) continue;
|
|
268
|
+
const c = info.get(rgba.subarray(o, o + 3).toString("hex"));
|
|
269
|
+
let part;
|
|
270
|
+
if (c.fixed) part = "keep";
|
|
271
|
+
else if (skin(c)) part = "skin";
|
|
272
|
+
else if (band === "hair") part = "hair";
|
|
273
|
+
else if (band === "face") part = hairColour(c) && !inFaceCentre(x, y) ? "hair" : "keep";
|
|
274
|
+
else if (band === "top") part = c.band === "hair" ? "hair" : "top";
|
|
275
|
+
else if (band === "bottom") part = "bottom";
|
|
276
|
+
else part = "keep";
|
|
277
|
+
out[y * width + x] = part;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return out;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** A part's target lightness for a source lightness: a monotone ramp around the part's mean. */
|
|
284
|
+
function rampOf(l, mean, target) {
|
|
285
|
+
if (l <= mean) return target * (l / mean);
|
|
286
|
+
const top = Math.min(0.92, target + 0.35);
|
|
287
|
+
return target + (top - target) * ((l - mean) / (1 - mean));
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* A character sheet in one person's colours: {hair, skin, top, bottom} as
|
|
292
|
+
* "rrggbb". Returns {width, height, rgba, parts}, parts being pixelParts.
|
|
293
|
+
*/
|
|
294
|
+
export function recolorSheet(sheet, colours) {
|
|
295
|
+
const parts = pixelParts(sheet);
|
|
296
|
+
const { rgba } = sheet;
|
|
297
|
+
const sum = {};
|
|
298
|
+
const n = {};
|
|
299
|
+
for (let i = 0; i < parts.length; i++) {
|
|
300
|
+
const p = parts[i];
|
|
301
|
+
if (!PARTS.includes(p)) continue;
|
|
302
|
+
const [, , l] = hsl(rgba[i * 4], rgba[i * 4 + 1], rgba[i * 4 + 2]);
|
|
303
|
+
sum[p] = (sum[p] || 0) + l;
|
|
304
|
+
n[p] = (n[p] || 0) + 1;
|
|
305
|
+
}
|
|
306
|
+
const targets = {};
|
|
307
|
+
for (const p of PARTS) {
|
|
308
|
+
if (!n[p]) continue;
|
|
309
|
+
if (!/^[0-9a-f]{6}$/i.test(colours[p] || "")) throw new Error(`no ${p} colour`);
|
|
310
|
+
targets[p] = hsl(...channels(colours[p]));
|
|
311
|
+
}
|
|
312
|
+
const out = Buffer.from(rgba);
|
|
313
|
+
const memo = new Map();
|
|
314
|
+
for (let i = 0; i < parts.length; i++) {
|
|
315
|
+
const p = parts[i];
|
|
316
|
+
if (!PARTS.includes(p)) continue;
|
|
317
|
+
const o = i * 4;
|
|
318
|
+
const key = p + rgba.subarray(o, o + 3).toString("hex");
|
|
319
|
+
let to = memo.get(key);
|
|
320
|
+
if (!to) {
|
|
321
|
+
const [th, ts, tl] = targets[p];
|
|
322
|
+
const [, , l] = hsl(rgba[o], rgba[o + 1], rgba[o + 2]);
|
|
323
|
+
to = rgbOf(th, ts, Math.min(0.94, Math.max(0.03, rampOf(l, sum[p] / n[p], tl))));
|
|
324
|
+
memo.set(key, to);
|
|
325
|
+
}
|
|
326
|
+
[out[o], out[o + 1], out[o + 2]] = to;
|
|
327
|
+
}
|
|
328
|
+
return { width: sheet.width, height: sheet.height, rgba: out, parts };
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** The employees as the page reads them, and their sheets: "char_<slot>" -> PNG bytes. */
|
|
332
|
+
export function employees() {
|
|
333
|
+
const { employees: list } = JSON.parse(readFileSync(join(ART, "employees.json"), "utf8"));
|
|
334
|
+
const sprites = new Map();
|
|
335
|
+
const slots = new Set();
|
|
336
|
+
const people = list.map((e) => {
|
|
337
|
+
if (!Number.isInteger(e.slot) || e.slot < 12 || e.slot > 99 || slots.has(e.slot))
|
|
338
|
+
throw new Error(`${e.role}: slot must be a unique integer from 12 to 99`);
|
|
339
|
+
if (!Number.isInteger(e.base) || e.base < 0 || e.base > 5)
|
|
340
|
+
throw new Error(`${e.role}: base is a character sheet from 0 to 5`);
|
|
341
|
+
slots.add(e.slot);
|
|
342
|
+
const sheet = recolorSheet(decodePng(readFileSync(join(ASSETS, `char_${e.base}.png`))), e);
|
|
343
|
+
sprites.set(`char_${e.slot}`, encodePng(sheet.width, sheet.height, sheet.rgba));
|
|
344
|
+
return { role: e.role, name: e.name, agent: e.agent, tier: e.tier, slot: e.slot };
|
|
345
|
+
});
|
|
346
|
+
return { people, sprites };
|
|
347
|
+
}
|
|
348
|
+
|
|
81
349
|
/** One art file as a PNG; throws on a size line, row length or letter that does not fit. */
|
|
82
350
|
export function renderArt(text, colors, name = "sprite") {
|
|
83
351
|
const lines = text.replace(/\r/g, "").trimEnd().split("\n");
|
|
@@ -150,11 +418,13 @@ export function build() {
|
|
|
150
418
|
const read = (f) => readFileSync(join(SRC, f), "utf8");
|
|
151
419
|
const strings = JSON.parse(read("strings.json"));
|
|
152
420
|
const sprites = atlas();
|
|
421
|
+
const staff = employees();
|
|
153
422
|
const script = [
|
|
154
423
|
'"use strict";',
|
|
155
424
|
"(() => {",
|
|
156
425
|
`const ATLAS = ${JSON.stringify(sprites)};`,
|
|
157
426
|
`const STRINGS = ${JSON.stringify(strings)};`,
|
|
427
|
+
`const EMPLOYEES = ${JSON.stringify(staff.people)};`,
|
|
158
428
|
'const spriteUrl = (name) => "/office/assets/" + name + ".png";',
|
|
159
429
|
read("world.js").trim(),
|
|
160
430
|
read("app.js").trim(),
|
|
@@ -166,6 +436,7 @@ export function build() {
|
|
|
166
436
|
const hash = createHash("sha256").update(script).update(style).update(read("shell.html"));
|
|
167
437
|
// Redrawn sprites change the page too, though no source text did.
|
|
168
438
|
for (const [name, png] of artSprites()) hash.update(name).update(png);
|
|
439
|
+
for (const [name, png] of staff.sprites) hash.update(name).update(png);
|
|
169
440
|
const build = hash.digest("hex").slice(0, 12);
|
|
170
441
|
// Text that would close the inline element early, or open an HTML comment
|
|
171
442
|
// inside it, turns source into markup.
|
|
@@ -181,7 +452,7 @@ export function build() {
|
|
|
181
452
|
|
|
182
453
|
const isMain = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
183
454
|
if (isMain) {
|
|
184
|
-
const art = artSprites();
|
|
455
|
+
const art = new Map([...artSprites(), ...employees().sprites]);
|
|
185
456
|
const check = process.argv.includes("--check");
|
|
186
457
|
if (!check) for (const [name, png] of art) writeFileSync(join(ASSETS, `${name}.png`), png);
|
|
187
458
|
const html = build();
|