talos-code 0.0.1 → 0.1.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/LICENSE +661 -0
- package/README.md +111 -3
- package/THIRD_PARTY_NOTICES.md +40 -0
- package/dist/archive/zip.js +73 -0
- package/dist/args.js +97 -0
- package/dist/automations/history-store.js +34 -0
- package/dist/automations/policy-store.js +43 -0
- package/dist/automations/runner.js +107 -0
- package/dist/automations/schedule.js +85 -0
- package/dist/automations/windows-task.js +53 -0
- package/dist/commands/advanced-cli.js +233 -0
- package/dist/commands/checkpoint-cli.js +45 -0
- package/dist/commands/config-cli.js +107 -0
- package/dist/commands/context.js +17 -0
- package/dist/commands/extensions-cli.js +207 -0
- package/dist/commands/project-cli.js +74 -0
- package/dist/commands/project-commands.js +143 -0
- package/dist/commands/provider-cli.js +185 -0
- package/dist/commands/services-cli.js +248 -0
- package/dist/commands/session-cli.js +171 -0
- package/dist/commands/system-cli.js +405 -0
- package/dist/config/commands.js +60 -0
- package/dist/config/load.js +382 -0
- package/dist/config/migrations.js +48 -0
- package/dist/config/types.js +11 -0
- package/dist/diagnostics/development-log.js +154 -0
- package/dist/diagnostics/doctor.js +104 -0
- package/dist/diagnostics/redact.js +80 -0
- package/dist/diagnostics/zip.js +52 -0
- package/dist/errors.js +156 -0
- package/dist/events/bridge.js +130 -0
- package/dist/extensions/installer.js +178 -0
- package/dist/extensions/package-schema.js +24 -0
- package/dist/headless/result.js +84 -0
- package/dist/headless/run.js +230 -0
- package/dist/i18n/en/approval.js +43 -0
- package/dist/i18n/en/common.js +9 -0
- package/dist/i18n/en/credentials.js +82 -0
- package/dist/i18n/en/errors.js +246 -0
- package/dist/i18n/en/firstrun.js +89 -0
- package/dist/i18n/en/providers.js +37 -0
- package/dist/i18n/en/screen.js +736 -0
- package/dist/i18n/en/tools.js +127 -0
- package/dist/i18n/en.js +14 -0
- package/dist/i18n/error-view.js +307 -0
- package/dist/i18n/index.js +19 -0
- package/dist/i18n/kernel-map.js +139 -0
- package/dist/io.js +45 -0
- package/dist/main.js +330 -0
- package/dist/paths.js +41 -0
- package/dist/protocol/v2/codec.js +9 -0
- package/dist/protocol/v2/events.js +555 -0
- package/dist/protocol/v2/index.js +4 -0
- package/dist/protocol/v2/replay.js +28 -0
- package/dist/protocol/v2/types.js +1 -0
- package/dist/provider/control-plane.js +135 -0
- package/dist/provider/environment-keys.js +275 -0
- package/dist/provider/health.js +110 -0
- package/dist/provider/missing-key.js +48 -0
- package/dist/provider/model-catalog.js +168 -0
- package/dist/provider/provider-text.js +13 -0
- package/dist/provider/store.js +95 -0
- package/dist/provider/system-keyring.js +214 -0
- package/dist/runtime/active-run.js +63 -0
- package/dist/runtime/agent-tree.js +74 -0
- package/dist/runtime/attachments.js +84 -0
- package/dist/runtime/brokered-executor.js +395 -0
- package/dist/runtime/context-status.js +76 -0
- package/dist/runtime/create-runtime.js +111 -0
- package/dist/runtime/model-profile.js +361 -0
- package/dist/runtime/output-store.js +137 -0
- package/dist/runtime/provider-attempts.js +185 -0
- package/dist/runtime/replay-buffer.js +153 -0
- package/dist/runtime/repo.js +95 -0
- package/dist/runtime/session-facade.js +135 -0
- package/dist/runtime/session-summary.js +125 -0
- package/dist/runtime/supervisor.js +262 -0
- package/dist/runtime/talos-composition.js +1006 -0
- package/dist/runtime/types.js +5 -0
- package/dist/runtime/usage-snapshot.js +82 -0
- package/dist/security/auto-classifier.js +38 -0
- package/dist/security/credential-free-environment.js +54 -0
- package/dist/security/evaluate.js +243 -0
- package/dist/security/execution-backends.js +236 -0
- package/dist/security/execution-broker.js +102 -0
- package/dist/security/forge-scan.js +74 -0
- package/dist/security/from-approval.js +39 -0
- package/dist/security/mxc-execution-backend.js +281 -0
- package/dist/security/permission-engine.js +138 -0
- package/dist/security/permission-explanation.js +54 -0
- package/dist/security/persist.js +179 -0
- package/dist/security/plugin-guard.js +230 -0
- package/dist/security/process-tree-evidence-store.js +74 -0
- package/dist/security/process-tree-probe.js +554 -0
- package/dist/security/project-resource-inventory.js +186 -0
- package/dist/security/project-trust-gate.js +38 -0
- package/dist/security/project-trust.js +367 -0
- package/dist/security/rule-parser.js +201 -0
- package/dist/security/shell-segmentation.js +68 -0
- package/dist/security/trust-authority.js +324 -0
- package/dist/security/types.js +1 -0
- package/dist/security/workspace-identity.js +102 -0
- package/dist/services/automation-facade.js +59 -0
- package/dist/services/forge-facade.js +77 -0
- package/dist/services/hook-facade.js +240 -0
- package/dist/services/index.js +16 -0
- package/dist/services/library-facade.js +174 -0
- package/dist/services/mcp-facade.js +348 -0
- package/dist/services/memory-facade.js +126 -0
- package/dist/services/notes-facade.js +157 -0
- package/dist/services/plugin-facade.js +308 -0
- package/dist/services/research-facade.js +110 -0
- package/dist/services/task-board-facade.js +236 -0
- package/dist/services/workflow-catalog.js +246 -0
- package/dist/sessions/export-format.js +98 -0
- package/dist/sessions/metadata-store.js +160 -0
- package/dist/sessions/share.js +122 -0
- package/dist/sessions/transfer.js +16 -0
- package/dist/subcommands.js +62 -0
- package/dist/tui/agent-roster.js +36 -0
- package/dist/tui/app.js +3761 -0
- package/dist/tui/approval.js +36 -0
- package/dist/tui/boot/boot-sequence.js +76 -0
- package/dist/tui/boot/cinematic.js +243 -0
- package/dist/tui/boot/desktop-logo.js +82 -0
- package/dist/tui/boot.js +3 -0
- package/dist/tui/busy-input.js +58 -0
- package/dist/tui/catalog-service.js +559 -0
- package/dist/tui/components/assistant-stream.js +1 -0
- package/dist/tui/components/command-menu.js +68 -0
- package/dist/tui/components/composer.js +219 -0
- package/dist/tui/components/diff.js +35 -0
- package/dist/tui/components/footer.js +48 -0
- package/dist/tui/components/header.js +6 -0
- package/dist/tui/components/markdown.js +305 -0
- package/dist/tui/components/status-indicator.js +32 -0
- package/dist/tui/components/terminal-shell.js +205 -0
- package/dist/tui/components/thinking-row.js +19 -0
- package/dist/tui/components/tool-row.js +97 -0
- package/dist/tui/components/transcript-virtualizer.js +165 -0
- package/dist/tui/components/transcript.js +43 -0
- package/dist/tui/diff-model.js +77 -0
- package/dist/tui/editor-history.js +21 -0
- package/dist/tui/editor.js +210 -0
- package/dist/tui/event-adapter.js +436 -0
- package/dist/tui/exit-output.js +58 -0
- package/dist/tui/external-editor.js +53 -0
- package/dist/tui/file-completion.js +89 -0
- package/dist/tui/focus-manager.js +5 -0
- package/dist/tui/highlight.js +30 -0
- package/dist/tui/input-router.js +18 -0
- package/dist/tui/interrupt.js +99 -0
- package/dist/tui/keybindings.js +194 -0
- package/dist/tui/keymap-resolver.js +91 -0
- package/dist/tui/launch-state.js +139 -0
- package/dist/tui/line-diff.js +57 -0
- package/dist/tui/live-activity.js +45 -0
- package/dist/tui/metrics.js +123 -0
- package/dist/tui/onboarding.js +36 -0
- package/dist/tui/overlays/agent-tree.js +39 -0
- package/dist/tui/overlays/approval-dialog.js +640 -0
- package/dist/tui/overlays/automation-center.js +51 -0
- package/dist/tui/overlays/checkpoint-picker.js +47 -0
- package/dist/tui/overlays/context-inspector.js +36 -0
- package/dist/tui/overlays/effort-line.js +110 -0
- package/dist/tui/overlays/forge-center.js +46 -0
- package/dist/tui/overlays/help-dialog.js +16 -0
- package/dist/tui/overlays/history-picker.js +37 -0
- package/dist/tui/overlays/hook-center.js +70 -0
- package/dist/tui/overlays/library-center.js +55 -0
- package/dist/tui/overlays/mcp-center.js +60 -0
- package/dist/tui/overlays/memory-center.js +63 -0
- package/dist/tui/overlays/model-picker.js +72 -0
- package/dist/tui/overlays/notes-center.js +53 -0
- package/dist/tui/overlays/overlay-host.js +8 -0
- package/dist/tui/overlays/plugin-center.js +76 -0
- package/dist/tui/overlays/provider-picker.js +145 -0
- package/dist/tui/overlays/queue-editor.js +32 -0
- package/dist/tui/overlays/research-center.js +59 -0
- package/dist/tui/overlays/scroll-window.js +234 -0
- package/dist/tui/overlays/session-picker.js +115 -0
- package/dist/tui/overlays/tasks-center.js +83 -0
- package/dist/tui/overlays/theme-picker.js +21 -0
- package/dist/tui/overlays/transcript-search.js +58 -0
- package/dist/tui/overlays/trust-center.js +29 -0
- package/dist/tui/overlays/workflow-center.js +46 -0
- package/dist/tui/project-file-index.js +214 -0
- package/dist/tui/project-references.js +85 -0
- package/dist/tui/project-trust-prompt.js +287 -0
- package/dist/tui/prompt-history-store.js +116 -0
- package/dist/tui/queue-store.js +110 -0
- package/dist/tui/regions/budget.js +59 -0
- package/dist/tui/regions/views.js +96 -0
- package/dist/tui/render-coordinator.js +148 -0
- package/dist/tui/render-scheduler.js +4 -0
- package/dist/tui/run.js +69 -0
- package/dist/tui/selection-list.js +29 -0
- package/dist/tui/session-controller.js +778 -0
- package/dist/tui/session-export.js +54 -0
- package/dist/tui/shell-input.js +35 -0
- package/dist/tui/shell-layout.js +48 -0
- package/dist/tui/shell-model.js +98 -0
- package/dist/tui/slash-commands.js +80 -0
- package/dist/tui/state.js +151 -0
- package/dist/tui/status-bar.js +125 -0
- package/dist/tui/status-view.js +40 -0
- package/dist/tui/terminal-capabilities.js +9 -0
- package/dist/tui/terminal-session.js +11 -0
- package/dist/tui/text-width.js +66 -0
- package/dist/tui/theme-catalog.js +25 -0
- package/dist/tui/theme-store.js +23 -0
- package/dist/tui/theme.js +23 -0
- package/dist/tui/tool-display.js +135 -0
- package/dist/tui/tool-facts.js +1 -0
- package/dist/tui/tools/bash-renderer.js +32 -0
- package/dist/tui/tools/change.js +61 -0
- package/dist/tui/tools/edit-renderer.js +17 -0
- package/dist/tui/tools/generic-renderer.js +13 -0
- package/dist/tui/tools/list-renderer.js +14 -0
- package/dist/tui/tools/read-renderer.js +13 -0
- package/dist/tui/tools/registry.js +20 -0
- package/dist/tui/tools/row-format.js +187 -0
- package/dist/tui/tools/search-renderer.js +38 -0
- package/dist/tui/tools/shared.js +97 -0
- package/dist/tui/tools/write-renderer.js +15 -0
- package/dist/tui/transcript-model.js +441 -0
- package/dist/tui/ui-preferences.js +79 -0
- package/dist/tui/usage-view.js +77 -0
- package/dist/tui/vim-mode.js +99 -0
- package/dist/update/check.js +15 -0
- package/dist/update/npm.js +51 -0
- package/dist/update/run.js +129 -0
- package/dist/version.js +2 -0
- package/dist/workspace/checkpoint-store.js +210 -0
- package/dist/workspace/checkpoint.js +413 -0
- package/dist/workspace/restore.js +232 -0
- package/package.json +63 -5
- package/vendor/context-engine/package.json +11 -0
- package/vendor/context-engine/src/compaction-planner.mjs +57 -0
- package/vendor/context-engine/src/contracts.mjs +48 -0
- package/vendor/context-engine/src/engine.mjs +445 -0
- package/vendor/context-engine/src/node/context-export.mjs +164 -0
- package/vendor/context-engine/src/node/legacy-import.mjs +92 -0
- package/vendor/context-engine/src/node/migrations/001-context.sql +126 -0
- package/vendor/context-engine/src/node/sqlite-store.mjs +85 -0
- package/vendor/context-engine/src/node/sqlite-worker.mjs +559 -0
- package/vendor/context-engine/src/profiles.mjs +10 -0
- package/vendor/context-engine/src/retrieval.mjs +104 -0
- package/vendor/context-engine/src/summary.mjs +97 -0
- package/vendor/context-engine/src/usage.mjs +34 -0
- package/vendor/harness-ui/package.json +38 -0
- package/vendor/harness-ui/src/acp-agent.mjs +350 -0
- package/vendor/harness-ui/src/agent-service.mjs +2188 -0
- package/vendor/harness-ui/src/agui-events.mjs +452 -0
- package/vendor/harness-ui/src/ambiente-solo-server.mjs +121 -0
- package/vendor/harness-ui/src/artifact-store.mjs +39 -0
- package/vendor/harness-ui/src/assistenza.mjs +123 -0
- package/vendor/harness-ui/src/automation-scheduler.mjs +69 -0
- package/vendor/harness-ui/src/automation-store.mjs +145 -0
- package/vendor/harness-ui/src/browser-annota.mjs +639 -0
- package/vendor/harness-ui/src/browser-frame.mjs +211 -0
- package/vendor/harness-ui/src/browser-proxy-universale.mjs +519 -0
- package/vendor/harness-ui/src/browser-proxy.mjs +87 -0
- package/vendor/harness-ui/src/browser-sessione-viva.mjs +329 -0
- package/vendor/harness-ui/src/browser-stream.mjs +445 -0
- package/vendor/harness-ui/src/browser-vivo.mjs +694 -0
- package/vendor/harness-ui/src/chat-image-attachments.mjs +72 -0
- package/vendor/harness-ui/src/config.mjs +697 -0
- package/vendor/harness-ui/src/contesto-del-progetto.mjs +385 -0
- package/vendor/harness-ui/src/context-asset-adapter.mjs +72 -0
- package/vendor/harness-ui/src/context-desktop-service.mjs +253 -0
- package/vendor/harness-ui/src/context-embedding-runtime.mjs +252 -0
- package/vendor/harness-ui/src/context-inference-scheduler.mjs +81 -0
- package/vendor/harness-ui/src/context-native-compaction.mjs +75 -0
- package/vendor/harness-ui/src/context-provider-adapter.mjs +141 -0
- package/vendor/harness-ui/src/context-runtime.mjs +118 -0
- package/vendor/harness-ui/src/context-token-counters.mjs +184 -0
- package/vendor/harness-ui/src/context-tool-catalog.mjs +86 -0
- package/vendor/harness-ui/src/context-tool-output.mjs +72 -0
- package/vendor/harness-ui/src/costo-elenco.mjs +252 -0
- package/vendor/harness-ui/src/custom-task.mjs +171 -0
- package/vendor/harness-ui/src/doctor.mjs +142 -0
- package/vendor/harness-ui/src/document-filename.mjs +97 -0
- package/vendor/harness-ui/src/document-generator.mjs +493 -0
- package/vendor/harness-ui/src/document-report.mjs +331 -0
- package/vendor/harness-ui/src/duckduckgo-search.mjs +155 -0
- package/vendor/harness-ui/src/elenco-profondo.mjs +337 -0
- package/vendor/harness-ui/src/favicon-proxy.mjs +113 -0
- package/vendor/harness-ui/src/forge-contract.mjs +221 -0
- package/vendor/harness-ui/src/frequent-dirs.mjs +134 -0
- package/vendor/harness-ui/src/generated-image-store.mjs +147 -0
- package/vendor/harness-ui/src/generation-idle.mjs +332 -0
- package/vendor/harness-ui/src/gguf-header.mjs +207 -0
- package/vendor/harness-ui/src/git-service.mjs +626 -0
- package/vendor/harness-ui/src/gitignore-elenco.mjs +604 -0
- package/vendor/harness-ui/src/harness-receipt-keypair.mjs +207 -0
- package/vendor/harness-ui/src/hf-direct-transfer.mjs +170 -0
- package/vendor/harness-ui/src/hf-hub-client.mjs +106 -0
- package/vendor/harness-ui/src/hf-image-proxy.mjs +105 -0
- package/vendor/harness-ui/src/hf-model-transfer.mjs +245 -0
- package/vendor/harness-ui/src/hook-registry.mjs +186 -0
- package/vendor/harness-ui/src/http-app.mjs +6259 -0
- package/vendor/harness-ui/src/http-lifecycle.mjs +132 -0
- package/vendor/harness-ui/src/id-archivio.mjs +27 -0
- package/vendor/harness-ui/src/image-generator.mjs +143 -0
- package/vendor/harness-ui/src/istruzioni-di-progetto.mjs +234 -0
- package/vendor/harness-ui/src/kernel/dist/kernelPerIlBanco.js +518 -0
- package/vendor/harness-ui/src/kernel/talosHarness.mjs +10437 -0
- package/vendor/harness-ui/src/library-policy-store.mjs +175 -0
- package/vendor/harness-ui/src/library-store.mjs +652 -0
- package/vendor/harness-ui/src/llama-server-supervisor.mjs +629 -0
- package/vendor/harness-ui/src/local-model-store.mjs +339 -0
- package/vendor/harness-ui/src/local-runtime-contract.mjs +66 -0
- package/vendor/harness-ui/src/local-runtime-events.mjs +44 -0
- package/vendor/harness-ui/src/local-runtime-llama-server.mjs +244 -0
- package/vendor/harness-ui/src/local-runtime-probe.mjs +401 -0
- package/vendor/harness-ui/src/machine-capacity.mjs +66 -0
- package/vendor/harness-ui/src/mappa-cartelle.mjs +491 -0
- package/vendor/harness-ui/src/mcp-client.mjs +98 -0
- package/vendor/harness-ui/src/mcp-registry.mjs +157 -0
- package/vendor/harness-ui/src/mcp-session.mjs +177 -0
- package/vendor/harness-ui/src/memory-store.mjs +227 -0
- package/vendor/harness-ui/src/model-catalog-models-dev.mjs +276 -0
- package/vendor/harness-ui/src/model-catalog.mjs +129 -0
- package/vendor/harness-ui/src/model-destination.mjs +189 -0
- package/vendor/harness-ui/src/modifica-ancorata.mjs +177 -0
- package/vendor/harness-ui/src/native-provider-adapter.mjs +205 -0
- package/vendor/harness-ui/src/notes-store.mjs +250 -0
- package/vendor/harness-ui/src/openai-compatible-runtime.mjs +428 -0
- package/vendor/harness-ui/src/openrouter-oauth.mjs +339 -0
- package/vendor/harness-ui/src/path-policy.mjs +442 -0
- package/vendor/harness-ui/src/plugin-registry.mjs +780 -0
- package/vendor/harness-ui/src/plugin-session.mjs +180 -0
- package/vendor/harness-ui/src/process-policy.mjs +345 -0
- package/vendor/harness-ui/src/prompt-enhancer-provider.mjs +94 -0
- package/vendor/harness-ui/src/provider-auth-cloud.mjs +95 -0
- package/vendor/harness-ui/src/provider-credential-store.mjs +541 -0
- package/vendor/harness-ui/src/provider-probe.mjs +582 -0
- package/vendor/harness-ui/src/provider-registry.mjs +1633 -0
- package/vendor/harness-ui/src/pty-terminal.mjs +312 -0
- package/vendor/harness-ui/src/public-problem.mjs +109 -0
- package/vendor/harness-ui/src/research/card.mjs +235 -0
- package/vendor/harness-ui/src/research/citations.mjs +142 -0
- package/vendor/harness-ui/src/research/collector.mjs +275 -0
- package/vendor/harness-ui/src/research/deposito-a-pezzi.mjs +139 -0
- package/vendor/harness-ui/src/research/dossier.mjs +114 -0
- package/vendor/harness-ui/src/research/esportazioni.mjs +560 -0
- package/vendor/harness-ui/src/research/fetch-cache.mjs +465 -0
- package/vendor/harness-ui/src/research/fidelity.mjs +122 -0
- package/vendor/harness-ui/src/research/independence.mjs +159 -0
- package/vendor/harness-ui/src/research/ledger.mjs +166 -0
- package/vendor/harness-ui/src/research/markdown-server.mjs +565 -0
- package/vendor/harness-ui/src/research/narration.mjs +181 -0
- package/vendor/harness-ui/src/research/open-cards.mjs +131 -0
- package/vendor/harness-ui/src/research/opposing.mjs +305 -0
- package/vendor/harness-ui/src/research/outline.mjs +111 -0
- package/vendor/harness-ui/src/research/page-budget.mjs +209 -0
- package/vendor/harness-ui/src/research/pdf.mjs +291 -0
- package/vendor/harness-ui/src/research/plan.mjs +301 -0
- package/vendor/harness-ui/src/research/raccolta-viva.mjs +452 -0
- package/vendor/harness-ui/src/research/recheck-document.mjs +69 -0
- package/vendor/harness-ui/src/research/recheck-history.mjs +192 -0
- package/vendor/harness-ui/src/research/recheck.mjs +194 -0
- package/vendor/harness-ui/src/research/report.mjs +203 -0
- package/vendor/harness-ui/src/research/run.mjs +527 -0
- package/vendor/harness-ui/src/research/synthesis.mjs +318 -0
- package/vendor/harness-ui/src/research/verification.mjs +572 -0
- package/vendor/harness-ui/src/research-orchestrator.mjs +2679 -0
- package/vendor/harness-ui/src/research-store.mjs +1133 -0
- package/vendor/harness-ui/src/runtime-build-manifest.mjs +26 -0
- package/vendor/harness-ui/src/runtime-contract.mjs +59 -0
- package/vendor/harness-ui/src/runtime-owner-adapter.mjs +1348 -0
- package/vendor/harness-ui/src/runtime-owner-contract.mjs +32 -0
- package/vendor/harness-ui/src/scheda-di-lavoro.mjs +249 -0
- package/vendor/harness-ui/src/search-source-store.mjs +172 -0
- package/vendor/harness-ui/src/session-registry.mjs +6095 -0
- package/vendor/harness-ui/src/session-store.mjs +220 -0
- package/vendor/harness-ui/src/sessione-pronta.mjs +73 -0
- package/vendor/harness-ui/src/setup-stato.mjs +31 -0
- package/vendor/harness-ui/src/sezioni-istruzioni.mjs +204 -0
- package/vendor/harness-ui/src/skill-registry.mjs +120 -0
- package/vendor/harness-ui/src/sse-replay-coalescente.mjs +0 -0
- package/vendor/harness-ui/src/static-files.mjs +96 -0
- package/vendor/harness-ui/src/stream-partition.mjs +123 -0
- package/vendor/harness-ui/src/subagent-orchestrator.mjs +453 -0
- package/vendor/harness-ui/src/task-catalog.mjs +65 -0
- package/vendor/harness-ui/src/tasks-store.mjs +220 -0
- package/vendor/harness-ui/src/terminal-registry.mjs +312 -0
- package/vendor/harness-ui/src/terminal-ws.mjs +170 -0
- package/vendor/harness-ui/src/tool-forge-store.mjs +299 -0
- package/vendor/harness-ui/src/tool-schema-normalize.mjs +100 -0
- package/vendor/harness-ui/src/usage-cache.mjs +315 -0
- package/vendor/harness-ui/src/workspace-browser.mjs +213 -0
- package/vendor/harness-ui/src/workspace-context.mjs +124 -0
- package/vendor/harness-ui/src/workspace-disk.mjs +62 -0
- package/vendor/harness-ui/src/workspace-files.mjs +589 -0
- package/vendor/harness-ui/src/workspace-info.mjs +189 -0
- package/vendor/harness-ui/src/workspace-launch-store.mjs +150 -0
- package/vendor/harness-ui/src/workspace-tree.mjs +67 -0
- package/vendor/harness-ui/src/workspace-watcher.mjs +161 -0
- package/vendor/manifest.json +170 -0
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Il filtro che decide che cosa NON entra nell'elenco profondo dei file dato al modello,
|
|
3
|
+
* perche' il progetto stesso lo considera ignorabile (`.gitignore`).
|
|
4
|
+
*
|
|
5
|
+
* ⛔ Perche' non e' «leggere un file e fare match»: sbagliare una regola qui significa
|
|
6
|
+
* NASCONDERE AL MODELLO FILE CHE ESISTONO — cioe' rifare, in un altro modo, il difetto
|
|
7
|
+
* che stiamo curando. Il 27/08/2026 un pattern `mobile/docs/` ha reso inerte
|
|
8
|
+
* `!docs/immagini/*.png` e per giorni ogni screenshot nuovo e' stato ignorato IN SILENZIO.
|
|
9
|
+
* Quel caso e' la prova numero uno della suite (`tests/gitignore-elenco.test.mjs`).
|
|
10
|
+
*
|
|
11
|
+
* ── FONTI ────────────────────────────────────────────────────────────────────────────
|
|
12
|
+
* [G] Git — `gitignore` Documentation, sezione PATTERN FORMAT.
|
|
13
|
+
* https://git-scm.com/docs/gitignore — letta il 10/09/2026.
|
|
14
|
+
* [P] POSIX / fnmatch(3p), flag FNM_PATHNAME + bracket expression negata con `!`.
|
|
15
|
+
* https://www.man7.org/linux/man-pages/man3/fnmatch.3p.html — letta il 10/09/2026.
|
|
16
|
+
* [O] ORACOLO: ogni regola qui sotto e' stata verificata contro `git check-ignore` VERO
|
|
17
|
+
* su un repo di prova, il 10/09/2026, prima di scrivere una riga di questo file.
|
|
18
|
+
*
|
|
19
|
+
* Citazioni verbatim da [G], che questo modulo implementa una per una:
|
|
20
|
+
*
|
|
21
|
+
* 1. «Trailing spaces are ignored unless they are quoted with backslash ("\").»
|
|
22
|
+
* → tagliaSpaziFinali()
|
|
23
|
+
* 2. «An optional prefix "!" which negates the pattern; any matching file excluded by a
|
|
24
|
+
* previous pattern will become included again.»
|
|
25
|
+
* → regola.negata, e l'ULTIMA regola che matcha decide (vedi decidiRiga()).
|
|
26
|
+
* 3. ⛔ «It is not possible to re-include a file if a parent directory of that file is
|
|
27
|
+
* excluded. Git doesn't list excluded directories for performance reasons, so any
|
|
28
|
+
* patterns on contained files have no effect, no matter where they are defined.»
|
|
29
|
+
* → il ciclo sui GENITORI dentro tieni(). E' il cuore del caso 27/08.
|
|
30
|
+
* 4. «If there is a separator at the beginning or middle (or both) of the pattern, then
|
|
31
|
+
* the pattern is relative to the directory level of the particular .gitignore file
|
|
32
|
+
* itself. Otherwise the pattern may also match at any level below the .gitignore level.»
|
|
33
|
+
* → ancorato / fluttuante in compilaRiga().
|
|
34
|
+
* 5. «If there is a separator at the end of the pattern then the pattern will only match
|
|
35
|
+
* directories, otherwise the pattern can match both files and directories.»
|
|
36
|
+
* → regola.soloDirectory.
|
|
37
|
+
* 6. «An asterisk "*" matches anything except a slash. The character "?" matches any one
|
|
38
|
+
* character except "/". The range notation, e.g. [a-zA-Z], can be used to match one of
|
|
39
|
+
* the characters in a range.»
|
|
40
|
+
* → globARegex(): `*` → [^/]*, `?` → [^/], `[...]` → classe che non attraversa mai `/` [P].
|
|
41
|
+
* 7. «A leading "**" followed by a slash means match in all directories.»
|
|
42
|
+
* «A trailing "/**" matches everything inside.»
|
|
43
|
+
* «A slash followed by two consecutive asterisks then a slash matches zero or more
|
|
44
|
+
* directories.» → i tre rami di `**` in globARegex().
|
|
45
|
+
* 8. «A backslash ("\") can be used to escape any character. […] a backslash at the end of
|
|
46
|
+
* a pattern is an invalid pattern that never matches.» → globARegex() + compilaRiga().
|
|
47
|
+
* 9. Annidati e precedenza: «Patterns read from a .gitignore file in the same directory as
|
|
48
|
+
* the path, or in any parent directory […] with patterns in the higher level files
|
|
49
|
+
* being overridden by those in lower level files down to the directory containing the
|
|
50
|
+
* file.» → le regole dei file piu' profondi entrano DOPO nell'elenco, quindi vincono.
|
|
51
|
+
*
|
|
52
|
+
* ── ⛔ COSA NON IMPLEMENTO (limiti DICHIARATI, non da scoprire) ───────────────────────
|
|
53
|
+
* L1. `core.excludesFile` (il gitignore globale dell'utente): richiede leggere la config
|
|
54
|
+
* git dell'utente. Fuori perimetro: un elenco per il modello deve mostrare il
|
|
55
|
+
* progetto, non le preferenze personali di chi lo apre. `.git/info/exclude` invece
|
|
56
|
+
* SI', perche' e' del repo (vedi FONTI_IMPLICITE).
|
|
57
|
+
* L2. `git check-ignore --no-index` e le regole da riga di comando: non esistono qui.
|
|
58
|
+
* L3. Le classi POSIX nominate — `[[:alpha:]]`, `[[:digit:]]` — sono passate alla regex
|
|
59
|
+
* cosi' come sono e NON funzionano. Sono rarissime in un `.gitignore` vero; se un
|
|
60
|
+
* giorno servono, il posto e' classeARegex().
|
|
61
|
+
* L4. Il case-folding di `core.ignoreCase`: qui il confronto e' SEMPRE sensibile alle
|
|
62
|
+
* maiuscole, anche su Windows dove il filesystem non lo e'. Scelta prudente: la
|
|
63
|
+
* direzione dell'errore e' «tengo un file di troppo», mai «ne nascondo uno».
|
|
64
|
+
* L5. I symlink a directory sono trattati come FILE (git fa lo stesso: non ci scende).
|
|
65
|
+
* L6. Un `\` dentro il pattern e' sempre un ESCAPE, mai un separatore di percorso. Su
|
|
66
|
+
* Windows un `.gitignore` con `src\build` non funziona — e non funziona neanche con
|
|
67
|
+
* git vero, quindi e' fedelta', non un limite nostro.
|
|
68
|
+
* L7. ⛔ Se la CARTELLA DI LAVORO STESSA e' ignorata (aprire `node_modules/` o una
|
|
69
|
+
* cartella che il repo padre esclude), il filtro NON svuota l'elenco: le regole dei
|
|
70
|
+
* genitori valgono per i percorsi DENTRO, ma la cartella aperta non si nasconde mai.
|
|
71
|
+
* Non e' un'invenzione: ripgrep fa lo stesso — `skip_entry` in `crates/ignore/src/walk.rs`
|
|
72
|
+
* «Returns Ok(false) without running any ignore/hidden checks for depth-0 entries,
|
|
73
|
+
* which are the paths explicitly provided as arguments» (letto via ctx7 il 10/09/2026).
|
|
74
|
+
* ⛔ E il verso opposto ha un costo misurato da un concorrente: Hermes Agent
|
|
75
|
+
* issue #45286 (giugno 2026) — l'albero file del Desktop filtra col `.gitignore`
|
|
76
|
+
* senza un interruttore «mostra ignorati», e in un monorepo le cartelle top-level
|
|
77
|
+
* possedute da repo figli SPARISCONO dalla vista pur esistendo sul disco.
|
|
78
|
+
* https://github.com/NousResearch/hermes-agent/issues/45286
|
|
79
|
+
* L8. `info/exclude` di un WORKTREE: lo cerco via `commondir`, ma se quel file manca o e'
|
|
80
|
+
* in una forma inattesa si rinuncia in silenzio — perdere `info/exclude` fa TENERE
|
|
81
|
+
* qualche file in piu', mai nasconderne.
|
|
82
|
+
*
|
|
83
|
+
* ── LA SCELTA: A MANO, non una libreria ──────────────────────────────────────────────
|
|
84
|
+
* La libreria di riferimento e' `ignore` (kaelzhang/node-ignore, 500+ test verificati
|
|
85
|
+
* contro `git check-ignore`). Non la aggiungo, per tre ragioni DICHIARATE:
|
|
86
|
+
* a) `package.json` non ha oggi NESSUN matcher di glob (ne' `ignore`, ne' `minimatch`,
|
|
87
|
+
* ne' `picomatch`): sarebbe una dipendenza NUOVA, e il progetto chiede una ragione
|
|
88
|
+
* dichiarata per ognuna. La ragione mancherebbe: vedi (b) e (c).
|
|
89
|
+
* b) Il contratto che il camminatore consuma e' `(percorsoRelativo, eDirectory)`, con
|
|
90
|
+
* `eDirectory` esplicito. `ignore` non ha quel parametro: vuole il suffisso `/` nel
|
|
91
|
+
* percorso, quindi andrebbe comunque avvolto — e l'avvolgimento e' il punto dove si
|
|
92
|
+
* sbaglia il caso 27/08.
|
|
93
|
+
* c) `ignore` non gestisce i `.gitignore` ANNIDATI: la composizione per base andrebbe
|
|
94
|
+
* scritta a mano lo stesso. Cioe' la meta' difficile del lavoro resterebbe nostra.
|
|
95
|
+
* d) ⛔ E `ignore` non risale ai `.gitignore` dei GENITORI: e' il buco misurato il
|
|
96
|
+
* 10/09/2026 partendo da `harness-ui/`, e la parte che ripgrep stesso ha corretto solo
|
|
97
|
+
* nella 15.0.0. Adottarla non ce l'avrebbe evitato.
|
|
98
|
+
* Costo misurato della via a mano: 300 righe di modulo (di cui ~110 di specifica citata)
|
|
99
|
+
* + 38 prove, e un oracolo `git check-ignore` per fissare la semantica prima di scrivere.
|
|
100
|
+
* Il perimetro coperto e' l'insieme dei costrutti che compaiono in un `.gitignore` reale;
|
|
101
|
+
* ogni buco noto e' dichiarato in L1-L8.
|
|
102
|
+
*
|
|
103
|
+
* ── PROVE (10/09/2026) ───────────────────────────────────────────────────────────────
|
|
104
|
+
* · 930 confronti contro `git check-ignore` su repo di prova: 0 disaccordi.
|
|
105
|
+
* · Da `harness-ui/` (sottocartella di un worktree): l'INSIEME dei file tenuti coincide
|
|
106
|
+
* esattamente con `git ls-files` + `git ls-files --others --exclude-standard` —
|
|
107
|
+
* 680 contro 680, zero in piu' e zero in meno, in entrambe le direzioni.
|
|
108
|
+
*/
|
|
109
|
+
|
|
110
|
+
import { promises as fsPromises } from 'node:fs';
|
|
111
|
+
import { dirname, isAbsolute, join, relative } from 'node:path';
|
|
112
|
+
|
|
113
|
+
/** Esiti di una valutazione. L'ultima regola che matcha decide [G, punto 2]. */
|
|
114
|
+
const ESCLUSO = 'escluso';
|
|
115
|
+
const INCLUSO = 'incluso';
|
|
116
|
+
const NESSUNA = 'nessuna';
|
|
117
|
+
|
|
118
|
+
/** Il nome del file di regole, e le fonti implicite lette da creaFiltroGitignore(). */
|
|
119
|
+
export const NOME_FILE_REGOLE = '.gitignore';
|
|
120
|
+
export const FONTI_IMPLICITE = Object.freeze(['.git/info/exclude']);
|
|
121
|
+
|
|
122
|
+
/** Tetti di sicurezza della scoperta dei file annidati. Dichiarati, non nascosti. */
|
|
123
|
+
const PROFONDITA_MASSIMA = 24;
|
|
124
|
+
const CARTELLE_MASSIME = 5000;
|
|
125
|
+
/**
|
|
126
|
+
* ⛔⛔⛔ 12/09/2026 — IL TETTO IN TEMPO. Owner: «quasi un minuto al primo messaggio». Misurato
|
|
127
|
+
* sulla cartella `Desktop` (che non e' un repo ma contiene decine di repo): questa raccolta
|
|
128
|
+
* legge 288 `.gitignore` per 6.002 regole in **12.342 ms**, prima ancora di elencare una cartella;
|
|
129
|
+
* sul repo `AVM-harness-desktop` 57 file, 969 regole, 1.593 ms. I tetti in cartelle (5.000) e in
|
|
130
|
+
* profondita' (24) non mordevano in tempo. Oltre il budget la raccolta si ferma e lo dichiara
|
|
131
|
+
* (`filtro.incompleto`): le regole non lette non nascondono niente — la direzione dell'errore
|
|
132
|
+
* resta «tengo di piu'», come per un file illeggibile.
|
|
133
|
+
*/
|
|
134
|
+
export const TEMPO_MASSIMO_RACCOLTA_MS = 1500;
|
|
135
|
+
/** ⛔ Quanti livelli si puo' RISALIRE cercando la radice del repo, per non finire su `C:\`. */
|
|
136
|
+
const RISALITA_MASSIMA = 64;
|
|
137
|
+
|
|
138
|
+
// ─────────────────────────────────────────────────────────────────────────────────────
|
|
139
|
+
// Parsing di UNA riga
|
|
140
|
+
// ─────────────────────────────────────────────────────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* [G] «Trailing spaces are ignored unless they are quoted with backslash ("\").»
|
|
144
|
+
* Solo lo SPAZIO, non il tab: git taglia ' ' (trim_trailing_spaces in dir.c).
|
|
145
|
+
* Uno spazio e' protetto se preceduto da un numero DISPARI di backslash.
|
|
146
|
+
*/
|
|
147
|
+
function tagliaSpaziFinali(riga) {
|
|
148
|
+
let fine = riga.length;
|
|
149
|
+
while (fine > 0 && riga[fine - 1] === ' ') {
|
|
150
|
+
let barre = 0;
|
|
151
|
+
for (let i = fine - 2; i >= 0 && riga[i] === '\\'; i -= 1) barre += 1;
|
|
152
|
+
if (barre % 2 === 1) break; // protetto: si ferma qui
|
|
153
|
+
fine -= 1;
|
|
154
|
+
}
|
|
155
|
+
return riga.slice(0, fine);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function scappaRegex(carattere) {
|
|
159
|
+
return carattere.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Trova la `]` che chiude una classe aperta in `apertura`, o -1 se non c'e'. */
|
|
163
|
+
function chiusuraClasse(pattern, apertura) {
|
|
164
|
+
let i = apertura + 1;
|
|
165
|
+
if (pattern[i] === '!' || pattern[i] === '^') i += 1;
|
|
166
|
+
if (pattern[i] === ']') i += 1; // una `]` in prima posizione e' letterale
|
|
167
|
+
while (i < pattern.length) {
|
|
168
|
+
if (pattern[i] === '\\') { i += 2; continue; }
|
|
169
|
+
if (pattern[i] === ']') return i;
|
|
170
|
+
i += 1;
|
|
171
|
+
}
|
|
172
|
+
return -1;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* [G] «The range notation, e.g. [a-zA-Z]» + [P] con FNM_PATHNAME la classe non matcha `/`.
|
|
177
|
+
* ⛔ L3: le classi POSIX nominate non sono tradotte.
|
|
178
|
+
*/
|
|
179
|
+
function classeARegex(testo) {
|
|
180
|
+
let dentro = testo.slice(1, -1);
|
|
181
|
+
let negata = false;
|
|
182
|
+
if (dentro.startsWith('!') || dentro.startsWith('^')) { negata = true; dentro = dentro.slice(1); }
|
|
183
|
+
// Dentro una classe regex vanno neutralizzati solo `\`, `]` e `^`; `-` e il resto passano.
|
|
184
|
+
const corpo = dentro.replace(/\\/g, '\\\\').replace(/\]/g, '\\]').replace(/\^/g, '\\^');
|
|
185
|
+
return negata ? `[^/${corpo}]` : `[${corpo.replace(/\//g, '')}]`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Traduce un glob gitignore in sorgente regex. Il `/` non e' mai attraversato da `*`, `?`
|
|
190
|
+
* o da una classe [G punto 6, P]; i tre casi di `**` sono quelli di [G punto 7].
|
|
191
|
+
*/
|
|
192
|
+
function globARegex(pattern) {
|
|
193
|
+
let re = '';
|
|
194
|
+
let i = 0;
|
|
195
|
+
while (i < pattern.length) {
|
|
196
|
+
const c = pattern[i];
|
|
197
|
+
|
|
198
|
+
if (c === '\\') { // [G punto 8] escape di qualunque carattere
|
|
199
|
+
if (i + 1 < pattern.length) { re += scappaRegex(pattern[i + 1]); i += 2; continue; }
|
|
200
|
+
i += 1; continue; // backslash finale: gia' scartato in compilaRiga()
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (c === '*') {
|
|
204
|
+
let j = i;
|
|
205
|
+
while (j < pattern.length && pattern[j] === '*') j += 1;
|
|
206
|
+
const quanti = j - i;
|
|
207
|
+
const primaSlash = i === 0 || pattern[i - 1] === '/';
|
|
208
|
+
const dopoSlash = j >= pattern.length || pattern[j] === '/';
|
|
209
|
+
|
|
210
|
+
// ⛔ «Other consecutive asterisks are considered regular asterisks»: il ramo
|
|
211
|
+
// speciale vale SOLO per esattamente due, isolati fra slash o estremi.
|
|
212
|
+
if (quanti === 2 && primaSlash && dopoSlash) {
|
|
213
|
+
if (i === 0 && j < pattern.length) { // «**/» iniziale: in tutte le directory
|
|
214
|
+
re += '(?:.+/)?'; i = j + 1; continue;
|
|
215
|
+
}
|
|
216
|
+
if (j >= pattern.length) { // «/**» finale: tutto quel che c'e' dentro
|
|
217
|
+
re += '.+'; i = j; continue;
|
|
218
|
+
}
|
|
219
|
+
re = `${re.slice(0, -1)}(?:/.+)?/`; i = j + 1; continue; // «/**/»: zero o piu' directory
|
|
220
|
+
}
|
|
221
|
+
re += '[^/]*'; i = j; continue;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (c === '?') { re += '[^/]'; i += 1; continue; }
|
|
225
|
+
|
|
226
|
+
if (c === '[') {
|
|
227
|
+
const fine = chiusuraClasse(pattern, i);
|
|
228
|
+
if (fine === -1) { re += '\\['; i += 1; continue; } // `[` spaiata = letterale
|
|
229
|
+
re += classeARegex(pattern.slice(i, fine + 1));
|
|
230
|
+
i = fine + 1; continue;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
re += scappaRegex(c); i += 1;
|
|
234
|
+
}
|
|
235
|
+
return re;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Compila UNA riga in una regola, o null se la riga non e' una regola
|
|
240
|
+
* (vuota, commento, o pattern invalido).
|
|
241
|
+
* @param {string} riga
|
|
242
|
+
* @param {string} base cartella del `.gitignore` che l'ha prodotta, con `/`, senza slash finale
|
|
243
|
+
*/
|
|
244
|
+
export function compilaRiga(riga, base = '') {
|
|
245
|
+
let p = typeof riga === 'string' ? riga : '';
|
|
246
|
+
if (p.endsWith('\r')) p = p.slice(0, -1); // file CRLF: su Windows e' la norma, non l'eccezione
|
|
247
|
+
p = tagliaSpaziFinali(p); // [G punto 1]
|
|
248
|
+
if (p === '') return null; // «A blank line matches no files»
|
|
249
|
+
if (p.startsWith('#')) return null; // «A line starting with # serves as a comment»
|
|
250
|
+
|
|
251
|
+
let negata = false;
|
|
252
|
+
if (p.startsWith('!')) { negata = true; p = p.slice(1); } // [G punto 2]
|
|
253
|
+
if (p === '') return null;
|
|
254
|
+
|
|
255
|
+
let soloDirectory = false;
|
|
256
|
+
if (p.endsWith('/')) { soloDirectory = true; p = p.slice(0, -1); } // [G punto 5]
|
|
257
|
+
if (p === '') return null;
|
|
258
|
+
|
|
259
|
+
// [G punto 8] «a backslash at the end of a pattern is an invalid pattern that never matches»
|
|
260
|
+
let barreFinali = 0;
|
|
261
|
+
for (let i = p.length - 1; i >= 0 && p[i] === '\\'; i -= 1) barreFinali += 1;
|
|
262
|
+
if (barreFinali % 2 === 1) return null;
|
|
263
|
+
|
|
264
|
+
// [G punto 4] uno slash all'inizio o in mezzo ancora il pattern alla base.
|
|
265
|
+
// Lo slash FINALE e' gia' stato tolto sopra, quindi non conta: `frotz/` resta fluttuante.
|
|
266
|
+
const ancorato = p.includes('/');
|
|
267
|
+
if (p.startsWith('/')) p = p.slice(1);
|
|
268
|
+
if (p === '') return null;
|
|
269
|
+
|
|
270
|
+
const corpo = globARegex(p);
|
|
271
|
+
const prefisso = ancorato ? '' : '(?:.*/)?'; // fluttuante: «may also match at any level below»
|
|
272
|
+
return {
|
|
273
|
+
negata,
|
|
274
|
+
soloDirectory,
|
|
275
|
+
base,
|
|
276
|
+
origine: riga,
|
|
277
|
+
regex: new RegExp(`^${prefisso}${corpo}$`),
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ─────────────────────────────────────────────────────────────────────────────────────
|
|
282
|
+
// Compilazione di un insieme di regole
|
|
283
|
+
// ─────────────────────────────────────────────────────────────────────────────────────
|
|
284
|
+
|
|
285
|
+
function normalizzaPercorso(percorso) {
|
|
286
|
+
if (typeof percorso !== 'string') return '';
|
|
287
|
+
let p = percorso.replace(/\\/g, '/');
|
|
288
|
+
while (p.startsWith('./')) p = p.slice(2);
|
|
289
|
+
while (p.startsWith('/')) p = p.slice(1);
|
|
290
|
+
while (p.endsWith('/')) p = p.slice(0, -1);
|
|
291
|
+
return p;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Unisce piu' gruppi di righe, ognuno con la sua base, in un solo giudice.
|
|
296
|
+
* ⛔ L'ORDINE E' LA PRECEDENZA [G punto 9]: chi arriva dopo vince. Chi chiama passa
|
|
297
|
+
* prima le fonti deboli (`.git/info/exclude`), poi la radice, poi gli annidati per
|
|
298
|
+
* profondita' crescente.
|
|
299
|
+
* @param {{righe: string[], base?: string}[]} gruppi
|
|
300
|
+
*/
|
|
301
|
+
export function componiRegole(gruppi) {
|
|
302
|
+
const regole = [];
|
|
303
|
+
for (const gruppo of gruppi ?? []) {
|
|
304
|
+
const base = normalizzaPercorso(gruppo?.base ?? '');
|
|
305
|
+
for (const riga of gruppo?.righe ?? []) {
|
|
306
|
+
const regola = compilaRiga(riga, base);
|
|
307
|
+
if (regola) regole.push(regola);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return creaGiudice(regole);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Le regole in forma pura, per poterle provare senza disco.
|
|
315
|
+
* @param {string[]|string} righe
|
|
316
|
+
* @param {{base?: string}} opzioni
|
|
317
|
+
* @returns {{tieni: (percorso: string, eDirectory?: boolean) => boolean, quante: number}}
|
|
318
|
+
*/
|
|
319
|
+
export function compilaRegole(righe, { base = '' } = {}) {
|
|
320
|
+
const elenco = typeof righe === 'string' ? righe.split('\n') : (righe ?? []);
|
|
321
|
+
return componiRegole([{ righe: elenco, base }]);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function creaGiudice(regole) {
|
|
325
|
+
const memoria = new Map();
|
|
326
|
+
|
|
327
|
+
/*
|
|
328
|
+
* ⛔ 12/09/2026 — LE REGOLE SI INDICIZZANO PER BASE, non si scorrono tutte. Con 6.002 regole
|
|
329
|
+
* (Desktop) ogni percorso nuovo costava 0,265 ms, e una mappa che conta i file di migliaia di
|
|
330
|
+
* cartelle ne chiede decine di migliaia: secondi interi spesi a confrontare `a/b/c` con regole
|
|
331
|
+
* che vivono in `progetti/x/y/` e non possono riguardarlo. Una regola con base `B` puo' matchare
|
|
332
|
+
* solo un percorso che inizia con `B/`: quindi per un percorso si guardano SOLO le regole della
|
|
333
|
+
* radice e dei suoi antenati. L'ordine resta quello di prima — le basi dalla piu' corta alla piu'
|
|
334
|
+
* lunga, e dentro ogni base l'ordine di inserimento — cioe' «piu' si scende piu' si vince»
|
|
335
|
+
* [G punto 9] e «l'ultima che matcha decide» [G punto 2] valgono identici: due basi diverse
|
|
336
|
+
* sullo stesso ramo sono sempre una antenata dell'altra, e la raccolta le aggiunge in ampiezza.
|
|
337
|
+
*/
|
|
338
|
+
const perBase = new Map();
|
|
339
|
+
for (const regola of regole) {
|
|
340
|
+
const gruppo = perBase.get(regola.base);
|
|
341
|
+
if (gruppo) gruppo.push(regola); else perBase.set(regola.base, [regola]);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/** L'ultima regola che matcha decide [G punto 2]. */
|
|
345
|
+
function decidiRiga(percorso, eDirectory) {
|
|
346
|
+
const chiave = `${eDirectory ? 'd' : 'f'}:${percorso}`;
|
|
347
|
+
const gia = memoria.get(chiave);
|
|
348
|
+
if (gia !== undefined) return gia;
|
|
349
|
+
|
|
350
|
+
let esito = NESSUNA;
|
|
351
|
+
/* Le basi candidate: '' e ogni antenato del percorso, dalla radice in giu'. */
|
|
352
|
+
const basi = [''];
|
|
353
|
+
for (let i = percorso.indexOf('/'); i !== -1; i = percorso.indexOf('/', i + 1)) basi.push(percorso.slice(0, i));
|
|
354
|
+
for (const base of basi) {
|
|
355
|
+
const gruppo = perBase.get(base);
|
|
356
|
+
if (!gruppo) continue;
|
|
357
|
+
const porzione = base === '' ? percorso : percorso.slice(base.length + 1);
|
|
358
|
+
if (porzione === '') continue;
|
|
359
|
+
for (const regola of gruppo) {
|
|
360
|
+
if (regola.soloDirectory && !eDirectory) continue; // [G punto 5]
|
|
361
|
+
if (!regola.regex.test(porzione)) continue;
|
|
362
|
+
esito = regola.negata ? INCLUSO : ESCLUSO;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
memoria.set(chiave, esito);
|
|
366
|
+
return esito;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* true = TIENI. ⛔ Il ciclo sui genitori e' [G punto 3], la regola del 27/08: una
|
|
371
|
+
* negazione NON puo' ripescare un file se una cartella genitore e' esclusa. La faccio
|
|
372
|
+
* qui dentro e non solo nella potatura del camminatore, cosi' l'esito e' giusto anche
|
|
373
|
+
* se qualcuno interroga un percorso profondo di punto in bianco.
|
|
374
|
+
*/
|
|
375
|
+
function tieniDa(percorso, eDirectory = false, daSegmento = 1) {
|
|
376
|
+
const norm = normalizzaPercorso(percorso);
|
|
377
|
+
if (norm === '') return true; // la radice non si nasconde mai
|
|
378
|
+
const segmenti = norm.split('/');
|
|
379
|
+
for (let i = Math.max(1, daSegmento); i < segmenti.length; i += 1) {
|
|
380
|
+
if (decidiRiga(segmenti.slice(0, i).join('/'), true) === ESCLUSO) return false;
|
|
381
|
+
}
|
|
382
|
+
return decidiRiga(norm, Boolean(eDirectory)) !== ESCLUSO;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const tieni = (percorso, eDirectory = false) => tieniDa(percorso, eDirectory, 1);
|
|
386
|
+
|
|
387
|
+
return { tieni, tieniDa, quante: regole.length, regole };
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// ─────────────────────────────────────────────────────────────────────────────────────
|
|
391
|
+
// Lettura dal disco
|
|
392
|
+
// ─────────────────────────────────────────────────────────────────────────────────────
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* ⛔ Un file che non c'e', o che non si legge, NON deve far sparire niente dall'elenco.
|
|
396
|
+
* Torna [] e va avanti: la direzione dell'errore e' sempre «tengo di piu'».
|
|
397
|
+
*/
|
|
398
|
+
async function leggiRighe(fs, percorsoAssoluto) {
|
|
399
|
+
try {
|
|
400
|
+
const testo = await fs.readFile(percorsoAssoluto, 'utf8');
|
|
401
|
+
return typeof testo === 'string' ? testo.split('\n') : [];
|
|
402
|
+
} catch {
|
|
403
|
+
return [];
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Risale da `radice` cercando la cartella di lavoro del repo — quella che contiene `.git`.
|
|
409
|
+
*
|
|
410
|
+
* ⛔ `.git` puo' essere una DIRECTORY (repo normale) o un FILE (worktree: dentro c'e'
|
|
411
|
+
* `gitdir: <percorso>`). Vanno bene entrambi: questa sessione gira proprio in un worktree,
|
|
412
|
+
* e trattarne solo uno dei due avrebbe fatto fallire la risalita in silenzio.
|
|
413
|
+
*
|
|
414
|
+
* ⛔ Due tetti dichiarati, per non salire all'infinito verso `C:\`:
|
|
415
|
+
* - `risalitaMassima` livelli (default 64);
|
|
416
|
+
* - lo stop naturale quando `dirname(x) === x`, cioe' la radice del volume.
|
|
417
|
+
* Se non si trova nessun `.git`, ci si ferma alla radice DATA: una cartella di lavoro che
|
|
418
|
+
* non e' un repo non deve far salire il filtro sopra quello che l'utente ha aperto.
|
|
419
|
+
*/
|
|
420
|
+
async function trovaRadiceRepo(fs, radice, risalitaMassima) {
|
|
421
|
+
if (typeof fs.stat !== 'function') return null;
|
|
422
|
+
let corrente = radice;
|
|
423
|
+
for (let salite = 0; salite <= risalitaMassima; salite += 1) {
|
|
424
|
+
try {
|
|
425
|
+
await fs.stat(join(corrente, '.git')); // file o directory: basta che esista
|
|
426
|
+
return corrente;
|
|
427
|
+
} catch { /* non qui: si sale */ }
|
|
428
|
+
const sopra = dirname(corrente);
|
|
429
|
+
if (!sopra || sopra === corrente) return null; // radice del volume
|
|
430
|
+
corrente = sopra;
|
|
431
|
+
}
|
|
432
|
+
return null; // tetto raggiunto
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Dove sta `info/exclude` per questo repo.
|
|
437
|
+
* Repo normale: `<radiceRepo>/.git/info/exclude`.
|
|
438
|
+
* Worktree: `.git` e' un FILE con `gitdir:`, e le regole condivise stanno nel COMMON DIR,
|
|
439
|
+
* che il file `commondir` dentro il gitdir indica. Se qualcosa non torna si rinuncia:
|
|
440
|
+
* perdere `info/exclude` significa TENERE qualche file in piu', mai nasconderne.
|
|
441
|
+
*/
|
|
442
|
+
async function cartellaGit(fs, radiceRepo) {
|
|
443
|
+
const punto = join(radiceRepo, '.git');
|
|
444
|
+
try {
|
|
445
|
+
const stato = await fs.stat(punto);
|
|
446
|
+
if (stato?.isDirectory?.()) return punto;
|
|
447
|
+
} catch { return null; }
|
|
448
|
+
try {
|
|
449
|
+
const testo = await fs.readFile(punto, 'utf8');
|
|
450
|
+
const trovato = /^gitdir:\s*(.+)$/m.exec(String(testo));
|
|
451
|
+
if (!trovato) return null;
|
|
452
|
+
const gitdir = trovato[1].trim();
|
|
453
|
+
try {
|
|
454
|
+
const comune = String(await fs.readFile(join(gitdir, 'commondir'), 'utf8')).trim();
|
|
455
|
+
return isAbsolute(comune) ? comune : join(gitdir, comune);
|
|
456
|
+
} catch {
|
|
457
|
+
return gitdir;
|
|
458
|
+
}
|
|
459
|
+
} catch { return null; }
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Legge le regole da una cartella e restituisce il filtro da dare al camminatore.
|
|
464
|
+
*
|
|
465
|
+
* ⛔⛔ LA RACCOLTA PARTE DALLA RADICE DEL REPO, NON DALLA CARTELLA DI LAVORO.
|
|
466
|
+
* [G] «Patterns read from a .gitignore file in the same directory as the path, or IN ANY
|
|
467
|
+
* PARENT DIRECTORY (up to the top-level of the working tree) […]». Una cartella di lavoro
|
|
468
|
+
* e' quasi sempre una SOTTOCARTELLA del repo, e le regole che contano di piu' (quelle degli
|
|
469
|
+
* artefatti) stanno nel `.gitignore` di radice. Misurato il 10/09/2026 su `harness-ui/`:
|
|
470
|
+
* partendo dalla sottocartella si trovavano 7 regole da 2 fonti invece delle 940 da 56 che
|
|
471
|
+
* si vedono dalla radice, e 944 file di artefatti che git considera ignorati finivano
|
|
472
|
+
* nell'elenco dato al modello, mangiando il tetto di 1.500.
|
|
473
|
+
* ⇒ Il giudice ragiona in coordinate RELATIVE ALLA RADICE DEL REPO; il filtro pubblico
|
|
474
|
+
* riceve percorsi relativi alla cartella di lavoro e antepone il prefisso.
|
|
475
|
+
*
|
|
476
|
+
* ⛔ SCELTA DICHIARATA (L7): se la cartella di lavoro STESSA e' ignorata (aprire
|
|
477
|
+
* `node_modules/` come workspace), il filtro NON svuota l'elenco. Git li' non mostrerebbe
|
|
478
|
+
* niente, ma qui l'utente ha scelto quella cartella apposta, e un elenco vuoto sarebbe di
|
|
479
|
+
* nuovo «nascondere al modello file che esistono». I genitori si controllano solo DENTRO
|
|
480
|
+
* la cartella di lavoro; le regole dei genitori si applicano ai percorsi lo stesso.
|
|
481
|
+
*
|
|
482
|
+
* Cerca i `.gitignore` ANNIDATI scendendo in ampiezza e POTANDO con le regole gia' note:
|
|
483
|
+
* non entra in `node_modules` se il `.gitignore` di radice lo esclude, quindi il costo e'
|
|
484
|
+
* proporzionale alle cartelle che il camminatore visitera' comunque.
|
|
485
|
+
*
|
|
486
|
+
* @param {{radice: string, fs?: object, profonditaMassima?: number, cartelleMassime?: number,
|
|
487
|
+
* risalitaMassima?: number}} opzioni
|
|
488
|
+
* @returns {Promise<((percorsoRelativo: string, eDirectory?: boolean) => boolean) &
|
|
489
|
+
* {quante: number, fonti: string[], radiceRepo: string|null, prefissoLavoro: string}>}
|
|
490
|
+
*/
|
|
491
|
+
export async function creaFiltroGitignore({
|
|
492
|
+
radice,
|
|
493
|
+
fs = fsPromises,
|
|
494
|
+
profonditaMassima = PROFONDITA_MASSIMA,
|
|
495
|
+
cartelleMassime = CARTELLE_MASSIME,
|
|
496
|
+
risalitaMassima = RISALITA_MASSIMA,
|
|
497
|
+
tempoMassimoMs = TEMPO_MASSIMO_RACCOLTA_MS,
|
|
498
|
+
orologio = () => performance.now(),
|
|
499
|
+
} = {}) {
|
|
500
|
+
const regole = [];
|
|
501
|
+
const fonti = [];
|
|
502
|
+
const avvioMs = orologio();
|
|
503
|
+
const budgetMs = Number.isFinite(tempoMassimoMs) ? Math.max(0, tempoMassimoMs) : Infinity;
|
|
504
|
+
let incompleto = false;
|
|
505
|
+
|
|
506
|
+
const aggiungi = (righe, base, etichetta) => {
|
|
507
|
+
const prima = regole.length;
|
|
508
|
+
for (const riga of righe) {
|
|
509
|
+
const regola = compilaRiga(riga, base);
|
|
510
|
+
if (regola) regole.push(regola);
|
|
511
|
+
}
|
|
512
|
+
if (regole.length > prima) fonti.push(etichetta);
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
// ⛔ RISALITA FINO ALLA RADICE DEL REPO — e in che cosa DIVERGO da ripgrep.
|
|
516
|
+
// [R] ripgrep, crate `ignore`, `Ignore::add_parents()` in `crates/ignore/src/dir.rs`,
|
|
517
|
+
// sorgente letto il 10/09/2026:
|
|
518
|
+
// https://github.com/BurntSushi/ripgrep/blob/master/crates/ignore/src/dir.rs
|
|
519
|
+
// Accumula TUTTI gli antenati fino alla radice del filesystem e NON si ferma al repo:
|
|
520
|
+
// tiene un flag `has_git`, calcolato con `parent.join(".git").exists()`, e lo usa per
|
|
521
|
+
// la precedenza. ⭐ Da li' prendo la rilevazione con `exists()`, che copre `.git`
|
|
522
|
+
// DIRECTORY e `.git` FILE (worktree) senza distinguerli — e' il caso di oggi.
|
|
523
|
+
// ⛔ Ma qui mi FERMO al primo `.git`: un elenco per il modello non deve ereditare
|
|
524
|
+
// regole da cartelle fuori dal progetto che l'utente ha aperto.
|
|
525
|
+
// [R2] ripgrep CHANGELOG 15.0.0 (2025-10-15), letto il 10/09/2026: fra le correzioni,
|
|
526
|
+
// «a commonly reported bug related to applying gitignore rules from parent
|
|
527
|
+
// directories». ⇒ Il buco che sto chiudendo qui e' il difetto CLASSICO di questa
|
|
528
|
+
// funzione, non una svista locale: chi lo scrive lo sbaglia quasi sempre.
|
|
529
|
+
const radiceRepo = await trovaRadiceRepo(fs, radice, risalitaMassima);
|
|
530
|
+
const partenza = radiceRepo ?? radice;
|
|
531
|
+
// Il pezzo di percorso fra la radice del repo e la cartella di lavoro, con `/`.
|
|
532
|
+
const prefissoLavoro = radiceRepo ? normalizzaPercorso(relative(radiceRepo, radice)) : '';
|
|
533
|
+
const segmentiPrefisso = prefissoLavoro === '' ? 0 : prefissoLavoro.split('/').length;
|
|
534
|
+
const sulDisco = (base) => join(partenza, ...(base ? base.split('/') : []));
|
|
535
|
+
|
|
536
|
+
// `.git/` non entra mai in un elenco per il modello: git stesso non lo lista.
|
|
537
|
+
// Prima di tutto il resto, cosi' resta la regola piu' DEBOLE.
|
|
538
|
+
aggiungi(['.git/'], '', '(implicita) .git/');
|
|
539
|
+
|
|
540
|
+
// Precedenza piu' bassa dei `.gitignore`, quindi prima di loro [G, DESCRIPTION].
|
|
541
|
+
// ⛔ L1: `core.excludesFile` no — quello e' dell'utente, non del progetto.
|
|
542
|
+
const dotGit = radiceRepo ? await cartellaGit(fs, radiceRepo) : null;
|
|
543
|
+
if (dotGit) {
|
|
544
|
+
const righe = await leggiRighe(fs, join(dotGit, 'info', 'exclude'));
|
|
545
|
+
if (righe.length) aggiungi(righe, '', FONTI_IMPLICITE[0]);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// I `.gitignore` dei GENITORI, dalla radice del repo giu' fino alla cartella di lavoro
|
|
549
|
+
// (esclusa: quella la legge la camminata qui sotto). Ognuno con la SUA base, e piu' si
|
|
550
|
+
// scende piu' si vince [G punto 9].
|
|
551
|
+
const scaletta = prefissoLavoro === '' ? [] : prefissoLavoro.split('/');
|
|
552
|
+
for (let i = 0; i < scaletta.length; i += 1) {
|
|
553
|
+
const base = scaletta.slice(0, i).join('/');
|
|
554
|
+
const righe = await leggiRighe(fs, join(sulDisco(base), NOME_FILE_REGOLE));
|
|
555
|
+
if (righe.length) aggiungi(righe, base, base ? `${base}/${NOME_FILE_REGOLE}` : NOME_FILE_REGOLE);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Ampiezza dalla cartella di lavoro in giu'. Le basi restano relative al REPO.
|
|
559
|
+
const giudiceVivo = () => creaGiudice(regole);
|
|
560
|
+
const coda = [prefissoLavoro];
|
|
561
|
+
let visitate = 0;
|
|
562
|
+
|
|
563
|
+
while (coda.length > 0 && visitate < cartelleMassime) {
|
|
564
|
+
if (orologio() - avvioMs > budgetMs) { incompleto = true; break; } // tetto in tempo (12/09): si dichiara, non si nasconde
|
|
565
|
+
const base = coda.shift();
|
|
566
|
+
visitate += 1;
|
|
567
|
+
|
|
568
|
+
const righe = await leggiRighe(fs, join(sulDisco(base), NOME_FILE_REGOLE));
|
|
569
|
+
if (righe.length) aggiungi(righe, base, base ? `${base}/${NOME_FILE_REGOLE}` : NOME_FILE_REGOLE);
|
|
570
|
+
|
|
571
|
+
if (base.split('/').filter(Boolean).length - segmentiPrefisso >= profonditaMassima) continue;
|
|
572
|
+
|
|
573
|
+
let voci;
|
|
574
|
+
try {
|
|
575
|
+
voci = await fs.readdir(sulDisco(base), { withFileTypes: true });
|
|
576
|
+
} catch {
|
|
577
|
+
continue; // ⛔ una cartella illeggibile non nasconde nulla: semplicemente non porta regole
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const giudice = giudiceVivo();
|
|
581
|
+
for (const voce of voci) {
|
|
582
|
+
// ⛔ L5: un symlink a cartella non e' isDirectory() qui, e va bene: git non ci scende.
|
|
583
|
+
if (!voce.isDirectory?.()) continue;
|
|
584
|
+
const figlia = base ? `${base}/${voce.name}` : voce.name;
|
|
585
|
+
if (giudice.tieniDa(figlia, true, segmentiPrefisso + 1)) coda.push(figlia);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
const giudice = creaGiudice(regole);
|
|
590
|
+
const filtro = (percorsoRelativo, eDirectory = false) => {
|
|
591
|
+
const norm = normalizzaPercorso(percorsoRelativo);
|
|
592
|
+
if (norm === '') return true;
|
|
593
|
+
const dalRepo = prefissoLavoro === '' ? norm : `${prefissoLavoro}/${norm}`;
|
|
594
|
+
// ⛔ Il ciclo sui genitori parte SOTTO la cartella di lavoro: vedi L7.
|
|
595
|
+
return giudice.tieniDa(dalRepo, eDirectory, segmentiPrefisso + 1);
|
|
596
|
+
};
|
|
597
|
+
filtro.quante = giudice.quante;
|
|
598
|
+
filtro.fonti = fonti;
|
|
599
|
+
filtro.radiceRepo = radiceRepo;
|
|
600
|
+
filtro.prefissoLavoro = prefissoLavoro;
|
|
601
|
+
filtro.incompleto = incompleto || visitate >= cartelleMassime;
|
|
602
|
+
filtro.msRaccolta = Math.round(orologio() - avvioMs);
|
|
603
|
+
return filtro;
|
|
604
|
+
}
|