synorch 0.2.0 → 0.4.0-beta.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 +55 -1
- package/README.md +338 -28
- package/dist/application/doctor-service.d.ts +16 -0
- package/dist/application/doctor-service.d.ts.map +1 -1
- package/dist/application/doctor-service.js +230 -32
- package/dist/application/doctor-service.js.map +1 -1
- package/dist/application/generated-skill-doctor.d.ts +9 -0
- package/dist/application/generated-skill-doctor.d.ts.map +1 -0
- package/dist/application/generated-skill-doctor.js +433 -0
- package/dist/application/generated-skill-doctor.js.map +1 -0
- package/dist/application/project-discovery.d.ts +26 -1
- package/dist/application/project-discovery.d.ts.map +1 -1
- package/dist/application/project-discovery.js +99 -6
- package/dist/application/project-discovery.js.map +1 -1
- package/dist/application/safe-path.d.ts +19 -0
- package/dist/application/safe-path.d.ts.map +1 -0
- package/dist/application/safe-path.js +35 -0
- package/dist/application/safe-path.js.map +1 -0
- package/dist/application/size-ceiling.d.ts +15 -0
- package/dist/application/size-ceiling.d.ts.map +1 -0
- package/dist/application/size-ceiling.js +32 -0
- package/dist/application/size-ceiling.js.map +1 -0
- package/dist/application/structure-service.d.ts.map +1 -1
- package/dist/application/structure-service.js +10 -1
- package/dist/application/structure-service.js.map +1 -1
- package/dist/build-info.json +4 -0
- package/dist/cli.js +135 -18
- package/dist/cli.js.map +1 -1
- package/dist/domain/canonical-contracts.d.ts +69 -0
- package/dist/domain/canonical-contracts.d.ts.map +1 -0
- package/dist/domain/canonical-contracts.js +92 -0
- package/dist/domain/canonical-contracts.js.map +1 -0
- package/dist/domain/generated-skill.d.ts +55 -0
- package/dist/domain/generated-skill.d.ts.map +1 -0
- package/dist/domain/generated-skill.js +90 -0
- package/dist/domain/generated-skill.js.map +1 -0
- package/dist/domain/generation.d.ts +15 -2
- package/dist/domain/generation.d.ts.map +1 -1
- package/dist/domain/observation-ledger.d.ts +118 -0
- package/dist/domain/observation-ledger.d.ts.map +1 -0
- package/dist/domain/observation-ledger.js +183 -0
- package/dist/domain/observation-ledger.js.map +1 -0
- package/dist/domain/product.d.ts +1 -1
- package/dist/domain/product.d.ts.map +1 -1
- package/dist/domain/product.js +1 -1
- package/dist/domain/product.js.map +1 -1
- package/dist/domain/relative-path.d.ts +21 -0
- package/dist/domain/relative-path.d.ts.map +1 -0
- package/dist/domain/relative-path.js +43 -0
- package/dist/domain/relative-path.js.map +1 -0
- package/dist/domain/skill-packs.d.ts.map +1 -1
- package/dist/domain/skill-packs.js +1 -0
- package/dist/domain/skill-packs.js.map +1 -1
- package/dist/domain/suggest.d.ts +5 -0
- package/dist/domain/suggest.d.ts.map +1 -0
- package/dist/domain/suggest.js +32 -0
- package/dist/domain/suggest.js.map +1 -0
- package/dist/domain/zod-issues.d.ts +21 -0
- package/dist/domain/zod-issues.d.ts.map +1 -0
- package/dist/domain/zod-issues.js +27 -0
- package/dist/domain/zod-issues.js.map +1 -0
- package/dist/harness/auth/api-key.d.ts +13 -0
- package/dist/harness/auth/api-key.d.ts.map +1 -0
- package/dist/harness/auth/api-key.js +87 -0
- package/dist/harness/auth/api-key.js.map +1 -0
- package/dist/harness/auth/claude-bridge.d.ts +22 -0
- package/dist/harness/auth/claude-bridge.d.ts.map +1 -0
- package/dist/harness/auth/claude-bridge.js +92 -0
- package/dist/harness/auth/claude-bridge.js.map +1 -0
- package/dist/harness/auth/command.d.ts +17 -0
- package/dist/harness/auth/command.d.ts.map +1 -0
- package/dist/harness/auth/command.js +223 -0
- package/dist/harness/auth/command.js.map +1 -0
- package/dist/harness/auth/credential-store.d.ts +30 -0
- package/dist/harness/auth/credential-store.d.ts.map +1 -0
- package/dist/harness/auth/credential-store.js +270 -0
- package/dist/harness/auth/credential-store.js.map +1 -0
- package/dist/harness/auth/index.d.ts +13 -0
- package/dist/harness/auth/index.d.ts.map +1 -0
- package/dist/harness/auth/index.js +10 -0
- package/dist/harness/auth/index.js.map +1 -0
- package/dist/harness/auth/json-file.d.ts +8 -0
- package/dist/harness/auth/json-file.d.ts.map +1 -0
- package/dist/harness/auth/json-file.js +57 -0
- package/dist/harness/auth/json-file.js.map +1 -0
- package/dist/harness/auth/jwt.d.ts +11 -0
- package/dist/harness/auth/jwt.d.ts.map +1 -0
- package/dist/harness/auth/jwt.js +37 -0
- package/dist/harness/auth/jwt.js.map +1 -0
- package/dist/harness/auth/keychain.d.ts +39 -0
- package/dist/harness/auth/keychain.d.ts.map +1 -0
- package/dist/harness/auth/keychain.js +191 -0
- package/dist/harness/auth/keychain.js.map +1 -0
- package/dist/harness/auth/locks.d.ts +19 -0
- package/dist/harness/auth/locks.d.ts.map +1 -0
- package/dist/harness/auth/locks.js +102 -0
- package/dist/harness/auth/locks.js.map +1 -0
- package/dist/harness/auth/notices.d.ts +6 -0
- package/dist/harness/auth/notices.d.ts.map +1 -0
- package/dist/harness/auth/notices.js +29 -0
- package/dist/harness/auth/notices.js.map +1 -0
- package/dist/harness/auth/openai-chatgpt.d.ts +32 -0
- package/dist/harness/auth/openai-chatgpt.d.ts.map +1 -0
- package/dist/harness/auth/openai-chatgpt.js +446 -0
- package/dist/harness/auth/openai-chatgpt.js.map +1 -0
- package/dist/harness/auth/profile-state.d.ts +25 -0
- package/dist/harness/auth/profile-state.d.ts.map +1 -0
- package/dist/harness/auth/profile-state.js +77 -0
- package/dist/harness/auth/profile-state.js.map +1 -0
- package/dist/harness/auth/providers.d.ts +27 -0
- package/dist/harness/auth/providers.d.ts.map +1 -0
- package/dist/harness/auth/providers.js +66 -0
- package/dist/harness/auth/providers.js.map +1 -0
- package/dist/harness/auth/resolved-credential.d.ts +11 -0
- package/dist/harness/auth/resolved-credential.d.ts.map +1 -0
- package/dist/harness/auth/resolved-credential.js +35 -0
- package/dist/harness/auth/resolved-credential.js.map +1 -0
- package/dist/harness/cli/appearance.d.ts +28 -0
- package/dist/harness/cli/appearance.d.ts.map +1 -0
- package/dist/harness/cli/appearance.js +123 -0
- package/dist/harness/cli/appearance.js.map +1 -0
- package/dist/harness/cli/args.d.ts +429 -0
- package/dist/harness/cli/args.d.ts.map +1 -0
- package/dist/harness/cli/args.js +372 -0
- package/dist/harness/cli/args.js.map +1 -0
- package/dist/harness/cli/attachments.d.ts +44 -0
- package/dist/harness/cli/attachments.d.ts.map +1 -0
- package/dist/harness/cli/attachments.js +176 -0
- package/dist/harness/cli/attachments.js.map +1 -0
- package/dist/harness/cli/canonical.d.ts +64 -0
- package/dist/harness/cli/canonical.d.ts.map +1 -0
- package/dist/harness/cli/canonical.js +455 -0
- package/dist/harness/cli/canonical.js.map +1 -0
- package/dist/harness/cli/claude-native-approvals.d.ts +45 -0
- package/dist/harness/cli/claude-native-approvals.d.ts.map +1 -0
- package/dist/harness/cli/claude-native-approvals.js +183 -0
- package/dist/harness/cli/claude-native-approvals.js.map +1 -0
- package/dist/harness/cli/command-grants.d.ts +23 -0
- package/dist/harness/cli/command-grants.d.ts.map +1 -0
- package/dist/harness/cli/command-grants.js +76 -0
- package/dist/harness/cli/command-grants.js.map +1 -0
- package/dist/harness/cli/config-command.d.ts +98 -0
- package/dist/harness/cli/config-command.d.ts.map +1 -0
- package/dist/harness/cli/config-command.js +561 -0
- package/dist/harness/cli/config-command.js.map +1 -0
- package/dist/harness/cli/config.d.ts +429 -0
- package/dist/harness/cli/config.d.ts.map +1 -0
- package/dist/harness/cli/config.js +504 -0
- package/dist/harness/cli/config.js.map +1 -0
- package/dist/harness/cli/conversation.d.ts +35 -0
- package/dist/harness/cli/conversation.d.ts.map +1 -0
- package/dist/harness/cli/conversation.js +3755 -0
- package/dist/harness/cli/conversation.js.map +1 -0
- package/dist/harness/cli/doctor.d.ts +32 -0
- package/dist/harness/cli/doctor.d.ts.map +1 -0
- package/dist/harness/cli/doctor.js +329 -0
- package/dist/harness/cli/doctor.js.map +1 -0
- package/dist/harness/cli/effort-command.d.ts +41 -0
- package/dist/harness/cli/effort-command.d.ts.map +1 -0
- package/dist/harness/cli/effort-command.js +133 -0
- package/dist/harness/cli/effort-command.js.map +1 -0
- package/dist/harness/cli/extensions/agents.d.ts +40 -0
- package/dist/harness/cli/extensions/agents.d.ts.map +1 -0
- package/dist/harness/cli/extensions/agents.js +66 -0
- package/dist/harness/cli/extensions/agents.js.map +1 -0
- package/dist/harness/cli/extensions/catalog.d.ts +134 -0
- package/dist/harness/cli/extensions/catalog.d.ts.map +1 -0
- package/dist/harness/cli/extensions/catalog.js +213 -0
- package/dist/harness/cli/extensions/catalog.js.map +1 -0
- package/dist/harness/cli/extensions/claude-home.d.ts +33 -0
- package/dist/harness/cli/extensions/claude-home.d.ts.map +1 -0
- package/dist/harness/cli/extensions/claude-home.js +78 -0
- package/dist/harness/cli/extensions/claude-home.js.map +1 -0
- package/dist/harness/cli/extensions/controller.d.ts +51 -0
- package/dist/harness/cli/extensions/controller.d.ts.map +1 -0
- package/dist/harness/cli/extensions/controller.js +67 -0
- package/dist/harness/cli/extensions/controller.js.map +1 -0
- package/dist/harness/cli/extensions/hooks.d.ts +164 -0
- package/dist/harness/cli/extensions/hooks.d.ts.map +1 -0
- package/dist/harness/cli/extensions/hooks.js +442 -0
- package/dist/harness/cli/extensions/hooks.js.map +1 -0
- package/dist/harness/cli/extensions/index.d.ts +10 -0
- package/dist/harness/cli/extensions/index.d.ts.map +1 -0
- package/dist/harness/cli/extensions/index.js +10 -0
- package/dist/harness/cli/extensions/index.js.map +1 -0
- package/dist/harness/cli/extensions/markdown.d.ts +46 -0
- package/dist/harness/cli/extensions/markdown.d.ts.map +1 -0
- package/dist/harness/cli/extensions/markdown.js +139 -0
- package/dist/harness/cli/extensions/markdown.js.map +1 -0
- package/dist/harness/cli/extensions/plugins.d.ts +160 -0
- package/dist/harness/cli/extensions/plugins.d.ts.map +1 -0
- package/dist/harness/cli/extensions/plugins.js +645 -0
- package/dist/harness/cli/extensions/plugins.js.map +1 -0
- package/dist/harness/cli/extensions/tool-hooks.d.ts +28 -0
- package/dist/harness/cli/extensions/tool-hooks.d.ts.map +1 -0
- package/dist/harness/cli/extensions/tool-hooks.js +63 -0
- package/dist/harness/cli/extensions/tool-hooks.js.map +1 -0
- package/dist/harness/cli/extensions-command.d.ts +64 -0
- package/dist/harness/cli/extensions-command.d.ts.map +1 -0
- package/dist/harness/cli/extensions-command.js +639 -0
- package/dist/harness/cli/extensions-command.js.map +1 -0
- package/dist/harness/cli/help.d.ts +9 -0
- package/dist/harness/cli/help.d.ts.map +1 -0
- package/dist/harness/cli/help.js +191 -0
- package/dist/harness/cli/help.js.map +1 -0
- package/dist/harness/cli/index.d.ts +52 -0
- package/dist/harness/cli/index.d.ts.map +1 -0
- package/dist/harness/cli/index.js +297 -0
- package/dist/harness/cli/index.js.map +1 -0
- package/dist/harness/cli/inspect.d.ts +40 -0
- package/dist/harness/cli/inspect.d.ts.map +1 -0
- package/dist/harness/cli/inspect.js +185 -0
- package/dist/harness/cli/inspect.js.map +1 -0
- package/dist/harness/cli/mcp-command.d.ts +61 -0
- package/dist/harness/cli/mcp-command.d.ts.map +1 -0
- package/dist/harness/cli/mcp-command.js +519 -0
- package/dist/harness/cli/mcp-command.js.map +1 -0
- package/dist/harness/cli/memory-desk.d.ts +28 -0
- package/dist/harness/cli/memory-desk.d.ts.map +1 -0
- package/dist/harness/cli/memory-desk.js +275 -0
- package/dist/harness/cli/memory-desk.js.map +1 -0
- package/dist/harness/cli/model-picker.d.ts +44 -0
- package/dist/harness/cli/model-picker.d.ts.map +1 -0
- package/dist/harness/cli/model-picker.js +267 -0
- package/dist/harness/cli/model-picker.js.map +1 -0
- package/dist/harness/cli/onboarding.d.ts +29 -0
- package/dist/harness/cli/onboarding.d.ts.map +1 -0
- package/dist/harness/cli/onboarding.js +101 -0
- package/dist/harness/cli/onboarding.js.map +1 -0
- package/dist/harness/cli/orchestrate-tool.d.ts +60 -0
- package/dist/harness/cli/orchestrate-tool.d.ts.map +1 -0
- package/dist/harness/cli/orchestrate-tool.js +167 -0
- package/dist/harness/cli/orchestrate-tool.js.map +1 -0
- package/dist/harness/cli/orchestration-view.d.ts +57 -0
- package/dist/harness/cli/orchestration-view.d.ts.map +1 -0
- package/dist/harness/cli/orchestration-view.js +371 -0
- package/dist/harness/cli/orchestration-view.js.map +1 -0
- package/dist/harness/cli/outcome.d.ts +11 -0
- package/dist/harness/cli/outcome.d.ts.map +1 -0
- package/dist/harness/cli/outcome.js +70 -0
- package/dist/harness/cli/outcome.js.map +1 -0
- package/dist/harness/cli/panels/common.d.ts +30 -0
- package/dist/harness/cli/panels/common.d.ts.map +1 -0
- package/dist/harness/cli/panels/common.js +143 -0
- package/dist/harness/cli/panels/common.js.map +1 -0
- package/dist/harness/cli/panels/mcp.d.ts +18 -0
- package/dist/harness/cli/panels/mcp.d.ts.map +1 -0
- package/dist/harness/cli/panels/mcp.js +132 -0
- package/dist/harness/cli/panels/mcp.js.map +1 -0
- package/dist/harness/cli/panels/plugins.d.ts +8 -0
- package/dist/harness/cli/panels/plugins.d.ts.map +1 -0
- package/dist/harness/cli/panels/plugins.js +114 -0
- package/dist/harness/cli/panels/plugins.js.map +1 -0
- package/dist/harness/cli/panels/session.d.ts +54 -0
- package/dist/harness/cli/panels/session.d.ts.map +1 -0
- package/dist/harness/cli/panels/session.js +221 -0
- package/dist/harness/cli/panels/session.js.map +1 -0
- package/dist/harness/cli/panels/skills.d.ts +16 -0
- package/dist/harness/cli/panels/skills.d.ts.map +1 -0
- package/dist/harness/cli/panels/skills.js +146 -0
- package/dist/harness/cli/panels/skills.js.map +1 -0
- package/dist/harness/cli/project-profile.d.ts +87 -0
- package/dist/harness/cli/project-profile.d.ts.map +1 -0
- package/dist/harness/cli/project-profile.js +709 -0
- package/dist/harness/cli/project-profile.js.map +1 -0
- package/dist/harness/cli/project-scope.d.ts +2 -0
- package/dist/harness/cli/project-scope.d.ts.map +1 -0
- package/dist/harness/cli/project-scope.js +55 -0
- package/dist/harness/cli/project-scope.js.map +1 -0
- package/dist/harness/cli/renderers.d.ts +75 -0
- package/dist/harness/cli/renderers.d.ts.map +1 -0
- package/dist/harness/cli/renderers.js +162 -0
- package/dist/harness/cli/renderers.js.map +1 -0
- package/dist/harness/cli/rewind.d.ts +54 -0
- package/dist/harness/cli/rewind.d.ts.map +1 -0
- package/dist/harness/cli/rewind.js +80 -0
- package/dist/harness/cli/rewind.js.map +1 -0
- package/dist/harness/cli/role-policy.d.ts +8 -0
- package/dist/harness/cli/role-policy.d.ts.map +1 -0
- package/dist/harness/cli/role-policy.js +53 -0
- package/dist/harness/cli/role-policy.js.map +1 -0
- package/dist/harness/cli/run-summary.d.ts +16 -0
- package/dist/harness/cli/run-summary.d.ts.map +1 -0
- package/dist/harness/cli/run-summary.js +105 -0
- package/dist/harness/cli/run-summary.js.map +1 -0
- package/dist/harness/cli/runtime.d.ts +182 -0
- package/dist/harness/cli/runtime.d.ts.map +1 -0
- package/dist/harness/cli/runtime.js +999 -0
- package/dist/harness/cli/runtime.js.map +1 -0
- package/dist/harness/cli/scripted-script.d.ts +50 -0
- package/dist/harness/cli/scripted-script.d.ts.map +1 -0
- package/dist/harness/cli/scripted-script.js +110 -0
- package/dist/harness/cli/scripted-script.js.map +1 -0
- package/dist/harness/cli/session-git.d.ts +76 -0
- package/dist/harness/cli/session-git.d.ts.map +1 -0
- package/dist/harness/cli/session-git.js +146 -0
- package/dist/harness/cli/session-git.js.map +1 -0
- package/dist/harness/cli/session.d.ts +22 -0
- package/dist/harness/cli/session.d.ts.map +1 -0
- package/dist/harness/cli/session.js +203 -0
- package/dist/harness/cli/session.js.map +1 -0
- package/dist/harness/cli/slash-commands.d.ts +85 -0
- package/dist/harness/cli/slash-commands.d.ts.map +1 -0
- package/dist/harness/cli/slash-commands.js +114 -0
- package/dist/harness/cli/slash-commands.js.map +1 -0
- package/dist/harness/cli/startup-notices.d.ts +32 -0
- package/dist/harness/cli/startup-notices.d.ts.map +1 -0
- package/dist/harness/cli/startup-notices.js +93 -0
- package/dist/harness/cli/startup-notices.js.map +1 -0
- package/dist/harness/cli/terminal.d.ts +32 -0
- package/dist/harness/cli/terminal.d.ts.map +1 -0
- package/dist/harness/cli/terminal.js +21 -0
- package/dist/harness/cli/terminal.js.map +1 -0
- package/dist/harness/cli/transparency.d.ts +39 -0
- package/dist/harness/cli/transparency.d.ts.map +1 -0
- package/dist/harness/cli/transparency.js +491 -0
- package/dist/harness/cli/transparency.js.map +1 -0
- package/dist/harness/cli/trust.d.ts +40 -0
- package/dist/harness/cli/trust.d.ts.map +1 -0
- package/dist/harness/cli/trust.js +133 -0
- package/dist/harness/cli/trust.js.map +1 -0
- package/dist/harness/cli/usage-stats.d.ts +57 -0
- package/dist/harness/cli/usage-stats.d.ts.map +1 -0
- package/dist/harness/cli/usage-stats.js +341 -0
- package/dist/harness/cli/usage-stats.js.map +1 -0
- package/dist/harness/context/compaction.d.ts +69 -0
- package/dist/harness/context/compaction.d.ts.map +1 -0
- package/dist/harness/context/compaction.js +149 -0
- package/dist/harness/context/compaction.js.map +1 -0
- package/dist/harness/context/context-builder.d.ts +76 -0
- package/dist/harness/context/context-builder.d.ts.map +1 -0
- package/dist/harness/context/context-builder.js +367 -0
- package/dist/harness/context/context-builder.js.map +1 -0
- package/dist/harness/context/history.d.ts +31 -0
- package/dist/harness/context/history.d.ts.map +1 -0
- package/dist/harness/context/history.js +105 -0
- package/dist/harness/context/history.js.map +1 -0
- package/dist/harness/context/index.d.ts +11 -0
- package/dist/harness/context/index.d.ts.map +1 -0
- package/dist/harness/context/index.js +9 -0
- package/dist/harness/context/index.js.map +1 -0
- package/dist/harness/context/instructions.d.ts +30 -0
- package/dist/harness/context/instructions.d.ts.map +1 -0
- package/dist/harness/context/instructions.js +82 -0
- package/dist/harness/context/instructions.js.map +1 -0
- package/dist/harness/context/model-view.d.ts +46 -0
- package/dist/harness/context/model-view.d.ts.map +1 -0
- package/dist/harness/context/model-view.js +136 -0
- package/dist/harness/context/model-view.js.map +1 -0
- package/dist/harness/context/scope.d.ts +9 -0
- package/dist/harness/context/scope.d.ts.map +1 -0
- package/dist/harness/context/scope.js +50 -0
- package/dist/harness/context/scope.js.map +1 -0
- package/dist/harness/context/skills.d.ts +75 -0
- package/dist/harness/context/skills.d.ts.map +1 -0
- package/dist/harness/context/skills.js +109 -0
- package/dist/harness/context/skills.js.map +1 -0
- package/dist/harness/context/tokens.d.ts +10 -0
- package/dist/harness/context/tokens.d.ts.map +1 -0
- package/dist/harness/context/tokens.js +39 -0
- package/dist/harness/context/tokens.js.map +1 -0
- package/dist/harness/contracts/auth.d.ts +214 -0
- package/dist/harness/contracts/auth.d.ts.map +1 -0
- package/dist/harness/contracts/auth.js +120 -0
- package/dist/harness/contracts/auth.js.map +1 -0
- package/dist/harness/contracts/choice.d.ts +115 -0
- package/dist/harness/contracts/choice.d.ts.map +1 -0
- package/dist/harness/contracts/choice.js +115 -0
- package/dist/harness/contracts/choice.js.map +1 -0
- package/dist/harness/contracts/common.d.ts +119 -0
- package/dist/harness/contracts/common.d.ts.map +1 -0
- package/dist/harness/contracts/common.js +66 -0
- package/dist/harness/contracts/common.js.map +1 -0
- package/dist/harness/contracts/digest.d.ts +59 -0
- package/dist/harness/contracts/digest.d.ts.map +1 -0
- package/dist/harness/contracts/digest.js +88 -0
- package/dist/harness/contracts/digest.js.map +1 -0
- package/dist/harness/contracts/errors.d.ts +70 -0
- package/dist/harness/contracts/errors.d.ts.map +1 -0
- package/dist/harness/contracts/errors.js +87 -0
- package/dist/harness/contracts/errors.js.map +1 -0
- package/dist/harness/contracts/events.d.ts +3094 -0
- package/dist/harness/contracts/events.d.ts.map +1 -0
- package/dist/harness/contracts/events.js +426 -0
- package/dist/harness/contracts/events.js.map +1 -0
- package/dist/harness/contracts/evidence.d.ts +286 -0
- package/dist/harness/contracts/evidence.d.ts.map +1 -0
- package/dist/harness/contracts/evidence.js +234 -0
- package/dist/harness/contracts/evidence.js.map +1 -0
- package/dist/harness/contracts/ids.d.ts +77 -0
- package/dist/harness/contracts/ids.d.ts.map +1 -0
- package/dist/harness/contracts/ids.js +126 -0
- package/dist/harness/contracts/ids.js.map +1 -0
- package/dist/harness/contracts/index.d.ts +23 -0
- package/dist/harness/contracts/index.d.ts.map +1 -0
- package/dist/harness/contracts/index.js +23 -0
- package/dist/harness/contracts/index.js.map +1 -0
- package/dist/harness/contracts/jsonl.d.ts +3386 -0
- package/dist/harness/contracts/jsonl.d.ts.map +1 -0
- package/dist/harness/contracts/jsonl.js +87 -0
- package/dist/harness/contracts/jsonl.js.map +1 -0
- package/dist/harness/contracts/memory.d.ts +271 -0
- package/dist/harness/contracts/memory.d.ts.map +1 -0
- package/dist/harness/contracts/memory.js +152 -0
- package/dist/harness/contracts/memory.js.map +1 -0
- package/dist/harness/contracts/model.d.ts +1201 -0
- package/dist/harness/contracts/model.d.ts.map +1 -0
- package/dist/harness/contracts/model.js +386 -0
- package/dist/harness/contracts/model.js.map +1 -0
- package/dist/harness/contracts/packets.d.ts +945 -0
- package/dist/harness/contracts/packets.d.ts.map +1 -0
- package/dist/harness/contracts/packets.js +535 -0
- package/dist/harness/contracts/packets.js.map +1 -0
- package/dist/harness/contracts/panel.d.ts +140 -0
- package/dist/harness/contracts/panel.d.ts.map +1 -0
- package/dist/harness/contracts/panel.js +19 -0
- package/dist/harness/contracts/panel.js.map +1 -0
- package/dist/harness/contracts/paths.d.ts +58 -0
- package/dist/harness/contracts/paths.d.ts.map +1 -0
- package/dist/harness/contracts/paths.js +204 -0
- package/dist/harness/contracts/paths.js.map +1 -0
- package/dist/harness/contracts/policy.d.ts +459 -0
- package/dist/harness/contracts/policy.d.ts.map +1 -0
- package/dist/harness/contracts/policy.js +338 -0
- package/dist/harness/contracts/policy.js.map +1 -0
- package/dist/harness/contracts/projection.d.ts +111 -0
- package/dist/harness/contracts/projection.d.ts.map +1 -0
- package/dist/harness/contracts/projection.js +2 -0
- package/dist/harness/contracts/projection.js.map +1 -0
- package/dist/harness/contracts/renderer.d.ts +291 -0
- package/dist/harness/contracts/renderer.d.ts.map +1 -0
- package/dist/harness/contracts/renderer.js +36 -0
- package/dist/harness/contracts/renderer.js.map +1 -0
- package/dist/harness/contracts/roles.d.ts +28 -0
- package/dist/harness/contracts/roles.d.ts.map +1 -0
- package/dist/harness/contracts/roles.js +36 -0
- package/dist/harness/contracts/roles.js.map +1 -0
- package/dist/harness/contracts/runtime.d.ts +283 -0
- package/dist/harness/contracts/runtime.d.ts.map +1 -0
- package/dist/harness/contracts/runtime.js +13 -0
- package/dist/harness/contracts/runtime.js.map +1 -0
- package/dist/harness/contracts/state.d.ts +124 -0
- package/dist/harness/contracts/state.d.ts.map +1 -0
- package/dist/harness/contracts/state.js +172 -0
- package/dist/harness/contracts/state.js.map +1 -0
- package/dist/harness/contracts/store.d.ts +117 -0
- package/dist/harness/contracts/store.d.ts.map +1 -0
- package/dist/harness/contracts/store.js +54 -0
- package/dist/harness/contracts/store.js.map +1 -0
- package/dist/harness/contracts/tools.d.ts +296 -0
- package/dist/harness/contracts/tools.d.ts.map +1 -0
- package/dist/harness/contracts/tools.js +117 -0
- package/dist/harness/contracts/tools.js.map +1 -0
- package/dist/harness/contracts/views.d.ts +468 -0
- package/dist/harness/contracts/views.d.ts.map +1 -0
- package/dist/harness/contracts/views.js +2 -0
- package/dist/harness/contracts/views.js.map +1 -0
- package/dist/harness/core/driver.d.ts +53 -0
- package/dist/harness/core/driver.d.ts.map +1 -0
- package/dist/harness/core/driver.js +566 -0
- package/dist/harness/core/driver.js.map +1 -0
- package/dist/harness/core/envelope.d.ts +11 -0
- package/dist/harness/core/envelope.d.ts.map +1 -0
- package/dist/harness/core/envelope.js +35 -0
- package/dist/harness/core/envelope.js.map +1 -0
- package/dist/harness/core/index.d.ts +7 -0
- package/dist/harness/core/index.d.ts.map +1 -0
- package/dist/harness/core/index.js +5 -0
- package/dist/harness/core/index.js.map +1 -0
- package/dist/harness/core/projection.d.ts +10 -0
- package/dist/harness/core/projection.d.ts.map +1 -0
- package/dist/harness/core/projection.js +268 -0
- package/dist/harness/core/projection.js.map +1 -0
- package/dist/harness/core/recovery.d.ts +14 -0
- package/dist/harness/core/recovery.d.ts.map +1 -0
- package/dist/harness/core/recovery.js +115 -0
- package/dist/harness/core/recovery.js.map +1 -0
- package/dist/harness/core/stream.d.ts +23 -0
- package/dist/harness/core/stream.d.ts.map +1 -0
- package/dist/harness/core/stream.js +102 -0
- package/dist/harness/core/stream.js.map +1 -0
- package/dist/harness/core/testing.d.ts +80 -0
- package/dist/harness/core/testing.d.ts.map +1 -0
- package/dist/harness/core/testing.js +258 -0
- package/dist/harness/core/testing.js.map +1 -0
- package/dist/harness/mcp/approvals.d.ts +14 -0
- package/dist/harness/mcp/approvals.d.ts.map +1 -0
- package/dist/harness/mcp/approvals.js +60 -0
- package/dist/harness/mcp/approvals.js.map +1 -0
- package/dist/harness/mcp/client.d.ts +74 -0
- package/dist/harness/mcp/client.d.ts.map +1 -0
- package/dist/harness/mcp/client.js +214 -0
- package/dist/harness/mcp/client.js.map +1 -0
- package/dist/harness/mcp/config.d.ts +156 -0
- package/dist/harness/mcp/config.d.ts.map +1 -0
- package/dist/harness/mcp/config.js +180 -0
- package/dist/harness/mcp/config.js.map +1 -0
- package/dist/harness/mcp/errors.d.ts +24 -0
- package/dist/harness/mcp/errors.d.ts.map +1 -0
- package/dist/harness/mcp/errors.js +67 -0
- package/dist/harness/mcp/errors.js.map +1 -0
- package/dist/harness/mcp/index.d.ts +9 -0
- package/dist/harness/mcp/index.d.ts.map +1 -0
- package/dist/harness/mcp/index.js +9 -0
- package/dist/harness/mcp/index.js.map +1 -0
- package/dist/harness/mcp/manager.d.ts +115 -0
- package/dist/harness/mcp/manager.d.ts.map +1 -0
- package/dist/harness/mcp/manager.js +361 -0
- package/dist/harness/mcp/manager.js.map +1 -0
- package/dist/harness/mcp/oauth.d.ts +54 -0
- package/dist/harness/mcp/oauth.d.ts.map +1 -0
- package/dist/harness/mcp/oauth.js +253 -0
- package/dist/harness/mcp/oauth.js.map +1 -0
- package/dist/harness/mcp/tools.d.ts +24 -0
- package/dist/harness/mcp/tools.d.ts.map +1 -0
- package/dist/harness/mcp/tools.js +142 -0
- package/dist/harness/mcp/tools.js.map +1 -0
- package/dist/harness/memory/bootstrap.d.ts +55 -0
- package/dist/harness/memory/bootstrap.d.ts.map +1 -0
- package/dist/harness/memory/bootstrap.js +124 -0
- package/dist/harness/memory/bootstrap.js.map +1 -0
- package/dist/harness/memory/desk.d.ts +74 -0
- package/dist/harness/memory/desk.d.ts.map +1 -0
- package/dist/harness/memory/desk.js +211 -0
- package/dist/harness/memory/desk.js.map +1 -0
- package/dist/harness/memory/graph.d.ts +24 -0
- package/dist/harness/memory/graph.d.ts.map +1 -0
- package/dist/harness/memory/graph.js +92 -0
- package/dist/harness/memory/graph.js.map +1 -0
- package/dist/harness/memory/index.d.ts +12 -0
- package/dist/harness/memory/index.d.ts.map +1 -0
- package/dist/harness/memory/index.js +10 -0
- package/dist/harness/memory/index.js.map +1 -0
- package/dist/harness/memory/markdown-memory-store.d.ts +122 -0
- package/dist/harness/memory/markdown-memory-store.d.ts.map +1 -0
- package/dist/harness/memory/markdown-memory-store.js +419 -0
- package/dist/harness/memory/markdown-memory-store.js.map +1 -0
- package/dist/harness/memory/memory-command.d.ts +22 -0
- package/dist/harness/memory/memory-command.d.ts.map +1 -0
- package/dist/harness/memory/memory-command.js +335 -0
- package/dist/harness/memory/memory-command.js.map +1 -0
- package/dist/harness/memory/memory-index.d.ts +71 -0
- package/dist/harness/memory/memory-index.d.ts.map +1 -0
- package/dist/harness/memory/memory-index.js +206 -0
- package/dist/harness/memory/memory-index.js.map +1 -0
- package/dist/harness/memory/note-format.d.ts +20 -0
- package/dist/harness/memory/note-format.d.ts.map +1 -0
- package/dist/harness/memory/note-format.js +69 -0
- package/dist/harness/memory/note-format.js.map +1 -0
- package/dist/harness/memory/obsidian.d.ts +14 -0
- package/dist/harness/memory/obsidian.d.ts.map +1 -0
- package/dist/harness/memory/obsidian.js +64 -0
- package/dist/harness/memory/obsidian.js.map +1 -0
- package/dist/harness/memory/redaction.d.ts +15 -0
- package/dist/harness/memory/redaction.d.ts.map +1 -0
- package/dist/harness/memory/redaction.js +64 -0
- package/dist/harness/memory/redaction.js.map +1 -0
- package/dist/harness/memory/relations.d.ts +29 -0
- package/dist/harness/memory/relations.d.ts.map +1 -0
- package/dist/harness/memory/relations.js +117 -0
- package/dist/harness/memory/relations.js.map +1 -0
- package/dist/harness/memory/vault.d.ts +35 -0
- package/dist/harness/memory/vault.d.ts.map +1 -0
- package/dist/harness/memory/vault.js +194 -0
- package/dist/harness/memory/vault.js.map +1 -0
- package/dist/harness/orchestration/approval.d.ts +29 -0
- package/dist/harness/orchestration/approval.d.ts.map +1 -0
- package/dist/harness/orchestration/approval.js +86 -0
- package/dist/harness/orchestration/approval.js.map +1 -0
- package/dist/harness/orchestration/artifact-review.d.ts +219 -0
- package/dist/harness/orchestration/artifact-review.d.ts.map +1 -0
- package/dist/harness/orchestration/artifact-review.js +319 -0
- package/dist/harness/orchestration/artifact-review.js.map +1 -0
- package/dist/harness/orchestration/attempt-log.d.ts +53 -0
- package/dist/harness/orchestration/attempt-log.d.ts.map +1 -0
- package/dist/harness/orchestration/attempt-log.js +138 -0
- package/dist/harness/orchestration/attempt-log.js.map +1 -0
- package/dist/harness/orchestration/budget.d.ts +110 -0
- package/dist/harness/orchestration/budget.d.ts.map +1 -0
- package/dist/harness/orchestration/budget.js +197 -0
- package/dist/harness/orchestration/budget.js.map +1 -0
- package/dist/harness/orchestration/capabilities.d.ts +6 -0
- package/dist/harness/orchestration/capabilities.d.ts.map +1 -0
- package/dist/harness/orchestration/capabilities.js +80 -0
- package/dist/harness/orchestration/capabilities.js.map +1 -0
- package/dist/harness/orchestration/claims.d.ts +148 -0
- package/dist/harness/orchestration/claims.d.ts.map +1 -0
- package/dist/harness/orchestration/claims.js +64 -0
- package/dist/harness/orchestration/claims.js.map +1 -0
- package/dist/harness/orchestration/control-plane.d.ts +18 -0
- package/dist/harness/orchestration/control-plane.d.ts.map +1 -0
- package/dist/harness/orchestration/control-plane.js +74 -0
- package/dist/harness/orchestration/control-plane.js.map +1 -0
- package/dist/harness/orchestration/coordinator.d.ts +132 -0
- package/dist/harness/orchestration/coordinator.d.ts.map +1 -0
- package/dist/harness/orchestration/coordinator.js +1628 -0
- package/dist/harness/orchestration/coordinator.js.map +1 -0
- package/dist/harness/orchestration/delegation.d.ts +80 -0
- package/dist/harness/orchestration/delegation.d.ts.map +1 -0
- package/dist/harness/orchestration/delegation.js +81 -0
- package/dist/harness/orchestration/delegation.js.map +1 -0
- package/dist/harness/orchestration/evidence.d.ts +124 -0
- package/dist/harness/orchestration/evidence.d.ts.map +1 -0
- package/dist/harness/orchestration/evidence.js +576 -0
- package/dist/harness/orchestration/evidence.js.map +1 -0
- package/dist/harness/orchestration/factories.d.ts +13 -0
- package/dist/harness/orchestration/factories.d.ts.map +1 -0
- package/dist/harness/orchestration/factories.js +20 -0
- package/dist/harness/orchestration/factories.js.map +1 -0
- package/dist/harness/orchestration/git.d.ts +77 -0
- package/dist/harness/orchestration/git.d.ts.map +1 -0
- package/dist/harness/orchestration/git.js +214 -0
- package/dist/harness/orchestration/git.js.map +1 -0
- package/dist/harness/orchestration/index.d.ts +25 -0
- package/dist/harness/orchestration/index.d.ts.map +1 -0
- package/dist/harness/orchestration/index.js +23 -0
- package/dist/harness/orchestration/index.js.map +1 -0
- package/dist/harness/orchestration/isolation.d.ts +181 -0
- package/dist/harness/orchestration/isolation.d.ts.map +1 -0
- package/dist/harness/orchestration/isolation.js +1357 -0
- package/dist/harness/orchestration/isolation.js.map +1 -0
- package/dist/harness/orchestration/paths.d.ts +18 -0
- package/dist/harness/orchestration/paths.d.ts.map +1 -0
- package/dist/harness/orchestration/paths.js +56 -0
- package/dist/harness/orchestration/paths.js.map +1 -0
- package/dist/harness/orchestration/plan.d.ts +219 -0
- package/dist/harness/orchestration/plan.d.ts.map +1 -0
- package/dist/harness/orchestration/plan.js +537 -0
- package/dist/harness/orchestration/plan.js.map +1 -0
- package/dist/harness/orchestration/planner.d.ts +137 -0
- package/dist/harness/orchestration/planner.d.ts.map +1 -0
- package/dist/harness/orchestration/planner.js +162 -0
- package/dist/harness/orchestration/planner.js.map +1 -0
- package/dist/harness/orchestration/recorder.d.ts +23 -0
- package/dist/harness/orchestration/recorder.d.ts.map +1 -0
- package/dist/harness/orchestration/recorder.js +50 -0
- package/dist/harness/orchestration/recorder.js.map +1 -0
- package/dist/harness/orchestration/scheduler.d.ts +40 -0
- package/dist/harness/orchestration/scheduler.d.ts.map +1 -0
- package/dist/harness/orchestration/scheduler.js +157 -0
- package/dist/harness/orchestration/scheduler.js.map +1 -0
- package/dist/harness/orchestration/testing.d.ts +233 -0
- package/dist/harness/orchestration/testing.d.ts.map +1 -0
- package/dist/harness/orchestration/testing.js +734 -0
- package/dist/harness/orchestration/testing.js.map +1 -0
- package/dist/harness/orchestration/verification-preflight.d.ts +46 -0
- package/dist/harness/orchestration/verification-preflight.d.ts.map +1 -0
- package/dist/harness/orchestration/verification-preflight.js +104 -0
- package/dist/harness/orchestration/verification-preflight.js.map +1 -0
- package/dist/harness/orchestration/worker-control.d.ts +64 -0
- package/dist/harness/orchestration/worker-control.d.ts.map +1 -0
- package/dist/harness/orchestration/worker-control.js +199 -0
- package/dist/harness/orchestration/worker-control.js.map +1 -0
- package/dist/harness/orchestration/worker-manager.d.ts +225 -0
- package/dist/harness/orchestration/worker-manager.d.ts.map +1 -0
- package/dist/harness/orchestration/worker-manager.js +1347 -0
- package/dist/harness/orchestration/worker-manager.js.map +1 -0
- package/dist/harness/orchestration/workspace-digest.d.ts +39 -0
- package/dist/harness/orchestration/workspace-digest.d.ts.map +1 -0
- package/dist/harness/orchestration/workspace-digest.js +147 -0
- package/dist/harness/orchestration/workspace-digest.js.map +1 -0
- package/dist/harness/policy/approval-broker.d.ts +13 -0
- package/dist/harness/policy/approval-broker.d.ts.map +1 -0
- package/dist/harness/policy/approval-broker.js +26 -0
- package/dist/harness/policy/approval-broker.js.map +1 -0
- package/dist/harness/policy/command-classifier.d.ts +29 -0
- package/dist/harness/policy/command-classifier.d.ts.map +1 -0
- package/dist/harness/policy/command-classifier.js +459 -0
- package/dist/harness/policy/command-classifier.js.map +1 -0
- package/dist/harness/policy/command-rules.d.ts +56 -0
- package/dist/harness/policy/command-rules.d.ts.map +1 -0
- package/dist/harness/policy/command-rules.js +588 -0
- package/dist/harness/policy/command-rules.js.map +1 -0
- package/dist/harness/policy/config.d.ts +27 -0
- package/dist/harness/policy/config.d.ts.map +1 -0
- package/dist/harness/policy/config.js +37 -0
- package/dist/harness/policy/config.js.map +1 -0
- package/dist/harness/policy/engine.d.ts +67 -0
- package/dist/harness/policy/engine.d.ts.map +1 -0
- package/dist/harness/policy/engine.js +483 -0
- package/dist/harness/policy/engine.js.map +1 -0
- package/dist/harness/policy/exec-allowlist.d.ts +66 -0
- package/dist/harness/policy/exec-allowlist.d.ts.map +1 -0
- package/dist/harness/policy/exec-allowlist.js +449 -0
- package/dist/harness/policy/exec-allowlist.js.map +1 -0
- package/dist/harness/policy/index.d.ts +10 -0
- package/dist/harness/policy/index.d.ts.map +1 -0
- package/dist/harness/policy/index.js +8 -0
- package/dist/harness/policy/index.js.map +1 -0
- package/dist/harness/policy/secret-patterns.d.ts +12 -0
- package/dist/harness/policy/secret-patterns.d.ts.map +1 -0
- package/dist/harness/policy/secret-patterns.js +48 -0
- package/dist/harness/policy/secret-patterns.js.map +1 -0
- package/dist/harness/policy/shell-parser.d.ts +40 -0
- package/dist/harness/policy/shell-parser.d.ts.map +1 -0
- package/dist/harness/policy/shell-parser.js +278 -0
- package/dist/harness/policy/shell-parser.js.map +1 -0
- package/dist/harness/policy/workspace-trust.d.ts +37 -0
- package/dist/harness/policy/workspace-trust.d.ts.map +1 -0
- package/dist/harness/policy/workspace-trust.js +164 -0
- package/dist/harness/policy/workspace-trust.js.map +1 -0
- package/dist/harness/providers/anthropic-messages.d.ts +48 -0
- package/dist/harness/providers/anthropic-messages.d.ts.map +1 -0
- package/dist/harness/providers/anthropic-messages.js +360 -0
- package/dist/harness/providers/anthropic-messages.js.map +1 -0
- package/dist/harness/providers/assembler.d.ts +29 -0
- package/dist/harness/providers/assembler.d.ts.map +1 -0
- package/dist/harness/providers/assembler.js +130 -0
- package/dist/harness/providers/assembler.js.map +1 -0
- package/dist/harness/providers/authenticated-stream.d.ts +9 -0
- package/dist/harness/providers/authenticated-stream.d.ts.map +1 -0
- package/dist/harness/providers/authenticated-stream.js +81 -0
- package/dist/harness/providers/authenticated-stream.js.map +1 -0
- package/dist/harness/providers/catalog.d.ts +75 -0
- package/dist/harness/providers/catalog.d.ts.map +1 -0
- package/dist/harness/providers/catalog.js +124 -0
- package/dist/harness/providers/catalog.js.map +1 -0
- package/dist/harness/providers/claude-code/adapter.d.ts +96 -0
- package/dist/harness/providers/claude-code/adapter.d.ts.map +1 -0
- package/dist/harness/providers/claude-code/adapter.js +863 -0
- package/dist/harness/providers/claude-code/adapter.js.map +1 -0
- package/dist/harness/providers/claude-code/mcp-relay.d.ts +2 -0
- package/dist/harness/providers/claude-code/mcp-relay.d.ts.map +1 -0
- package/dist/harness/providers/claude-code/mcp-relay.js +27 -0
- package/dist/harness/providers/claude-code/mcp-relay.js.map +1 -0
- package/dist/harness/providers/claude-code/mcp-server.d.ts +40 -0
- package/dist/harness/providers/claude-code/mcp-server.d.ts.map +1 -0
- package/dist/harness/providers/claude-code/mcp-server.js +171 -0
- package/dist/harness/providers/claude-code/mcp-server.js.map +1 -0
- package/dist/harness/providers/claude-code/native.d.ts +36 -0
- package/dist/harness/providers/claude-code/native.d.ts.map +1 -0
- package/dist/harness/providers/claude-code/native.js +153 -0
- package/dist/harness/providers/claude-code/native.js.map +1 -0
- package/dist/harness/providers/claude-code/process.d.ts +48 -0
- package/dist/harness/providers/claude-code/process.d.ts.map +1 -0
- package/dist/harness/providers/claude-code/process.js +128 -0
- package/dist/harness/providers/claude-code/process.js.map +1 -0
- package/dist/harness/providers/codex-app-server.d.ts +8 -0
- package/dist/harness/providers/codex-app-server.d.ts.map +1 -0
- package/dist/harness/providers/codex-app-server.js +45 -0
- package/dist/harness/providers/codex-app-server.js.map +1 -0
- package/dist/harness/providers/effort.d.ts +51 -0
- package/dist/harness/providers/effort.d.ts.map +1 -0
- package/dist/harness/providers/effort.js +108 -0
- package/dist/harness/providers/effort.js.map +1 -0
- package/dist/harness/providers/errors.d.ts +34 -0
- package/dist/harness/providers/errors.d.ts.map +1 -0
- package/dist/harness/providers/errors.js +143 -0
- package/dist/harness/providers/errors.js.map +1 -0
- package/dist/harness/providers/grammar.d.ts +17 -0
- package/dist/harness/providers/grammar.d.ts.map +1 -0
- package/dist/harness/providers/grammar.js +89 -0
- package/dist/harness/providers/grammar.js.map +1 -0
- package/dist/harness/providers/http-stream.d.ts +40 -0
- package/dist/harness/providers/http-stream.d.ts.map +1 -0
- package/dist/harness/providers/http-stream.js +134 -0
- package/dist/harness/providers/http-stream.js.map +1 -0
- package/dist/harness/providers/index.d.ts +20 -0
- package/dist/harness/providers/index.d.ts.map +1 -0
- package/dist/harness/providers/index.js +17 -0
- package/dist/harness/providers/index.js.map +1 -0
- package/dist/harness/providers/responses.d.ts +36 -0
- package/dist/harness/providers/responses.d.ts.map +1 -0
- package/dist/harness/providers/responses.js +435 -0
- package/dist/harness/providers/responses.js.map +1 -0
- package/dist/harness/providers/reviewer-route.d.ts +14 -0
- package/dist/harness/providers/reviewer-route.d.ts.map +1 -0
- package/dist/harness/providers/reviewer-route.js +31 -0
- package/dist/harness/providers/reviewer-route.js.map +1 -0
- package/dist/harness/providers/router.d.ts +50 -0
- package/dist/harness/providers/router.d.ts.map +1 -0
- package/dist/harness/providers/router.js +295 -0
- package/dist/harness/providers/router.js.map +1 -0
- package/dist/harness/providers/scripted.d.ts +22 -0
- package/dist/harness/providers/scripted.d.ts.map +1 -0
- package/dist/harness/providers/scripted.js +111 -0
- package/dist/harness/providers/scripted.js.map +1 -0
- package/dist/harness/providers/sse.d.ts +13 -0
- package/dist/harness/providers/sse.d.ts.map +1 -0
- package/dist/harness/providers/sse.js +82 -0
- package/dist/harness/providers/sse.js.map +1 -0
- package/dist/harness/providers/testing.d.ts +35 -0
- package/dist/harness/providers/testing.d.ts.map +1 -0
- package/dist/harness/providers/testing.js +93 -0
- package/dist/harness/providers/testing.js.map +1 -0
- package/dist/harness/providers/web-search.d.ts +110 -0
- package/dist/harness/providers/web-search.d.ts.map +1 -0
- package/dist/harness/providers/web-search.js +476 -0
- package/dist/harness/providers/web-search.js.map +1 -0
- package/dist/harness/store/blob-store.d.ts +4 -0
- package/dist/harness/store/blob-store.d.ts.map +1 -0
- package/dist/harness/store/blob-store.js +79 -0
- package/dist/harness/store/blob-store.js.map +1 -0
- package/dist/harness/store/durable-file.d.ts +17 -0
- package/dist/harness/store/durable-file.d.ts.map +1 -0
- package/dist/harness/store/durable-file.js +84 -0
- package/dist/harness/store/durable-file.js.map +1 -0
- package/dist/harness/store/event-store.d.ts +56 -0
- package/dist/harness/store/event-store.d.ts.map +1 -0
- package/dist/harness/store/event-store.js +210 -0
- package/dist/harness/store/event-store.js.map +1 -0
- package/dist/harness/store/index.d.ts +8 -0
- package/dist/harness/store/index.d.ts.map +1 -0
- package/dist/harness/store/index.js +4 -0
- package/dist/harness/store/index.js.map +1 -0
- package/dist/harness/store/layout.d.ts +12 -0
- package/dist/harness/store/layout.d.ts.map +1 -0
- package/dist/harness/store/layout.js +27 -0
- package/dist/harness/store/layout.js.map +1 -0
- package/dist/harness/store/lease.d.ts +37 -0
- package/dist/harness/store/lease.d.ts.map +1 -0
- package/dist/harness/store/lease.js +326 -0
- package/dist/harness/store/lease.js.map +1 -0
- package/dist/harness/store/segments.d.ts +49 -0
- package/dist/harness/store/segments.d.ts.map +1 -0
- package/dist/harness/store/segments.js +216 -0
- package/dist/harness/store/segments.js.map +1 -0
- package/dist/harness/store/session-store.d.ts +18 -0
- package/dist/harness/store/session-store.d.ts.map +1 -0
- package/dist/harness/store/session-store.js +319 -0
- package/dist/harness/store/session-store.js.map +1 -0
- package/dist/harness/tools/builtin/control-tools.d.ts +57 -0
- package/dist/harness/tools/builtin/control-tools.d.ts.map +1 -0
- package/dist/harness/tools/builtin/control-tools.js +72 -0
- package/dist/harness/tools/builtin/control-tools.js.map +1 -0
- package/dist/harness/tools/builtin/glob.d.ts +15 -0
- package/dist/harness/tools/builtin/glob.d.ts.map +1 -0
- package/dist/harness/tools/builtin/glob.js +164 -0
- package/dist/harness/tools/builtin/glob.js.map +1 -0
- package/dist/harness/tools/builtin/process-bg.d.ts +72 -0
- package/dist/harness/tools/builtin/process-bg.d.ts.map +1 -0
- package/dist/harness/tools/builtin/process-bg.js +360 -0
- package/dist/harness/tools/builtin/process-bg.js.map +1 -0
- package/dist/harness/tools/builtin/process-tools.d.ts +46 -0
- package/dist/harness/tools/builtin/process-tools.d.ts.map +1 -0
- package/dist/harness/tools/builtin/process-tools.js +263 -0
- package/dist/harness/tools/builtin/process-tools.js.map +1 -0
- package/dist/harness/tools/builtin/read-media.d.ts +10 -0
- package/dist/harness/tools/builtin/read-media.d.ts.map +1 -0
- package/dist/harness/tools/builtin/read-media.js +128 -0
- package/dist/harness/tools/builtin/read-media.js.map +1 -0
- package/dist/harness/tools/builtin/read-tools.d.ts +29 -0
- package/dist/harness/tools/builtin/read-tools.d.ts.map +1 -0
- package/dist/harness/tools/builtin/read-tools.js +329 -0
- package/dist/harness/tools/builtin/read-tools.js.map +1 -0
- package/dist/harness/tools/builtin/shared.d.ts +47 -0
- package/dist/harness/tools/builtin/shared.d.ts.map +1 -0
- package/dist/harness/tools/builtin/shared.js +94 -0
- package/dist/harness/tools/builtin/shared.js.map +1 -0
- package/dist/harness/tools/builtin/todo.d.ts +38 -0
- package/dist/harness/tools/builtin/todo.d.ts.map +1 -0
- package/dist/harness/tools/builtin/todo.js +55 -0
- package/dist/harness/tools/builtin/todo.js.map +1 -0
- package/dist/harness/tools/builtin/web-common.d.ts +18 -0
- package/dist/harness/tools/builtin/web-common.d.ts.map +1 -0
- package/dist/harness/tools/builtin/web-common.js +53 -0
- package/dist/harness/tools/builtin/web-common.js.map +1 -0
- package/dist/harness/tools/builtin/web-fetch.d.ts +33 -0
- package/dist/harness/tools/builtin/web-fetch.d.ts.map +1 -0
- package/dist/harness/tools/builtin/web-fetch.js +173 -0
- package/dist/harness/tools/builtin/web-fetch.js.map +1 -0
- package/dist/harness/tools/builtin/web-html.d.ts +14 -0
- package/dist/harness/tools/builtin/web-html.d.ts.map +1 -0
- package/dist/harness/tools/builtin/web-html.js +314 -0
- package/dist/harness/tools/builtin/web-html.js.map +1 -0
- package/dist/harness/tools/builtin/web-net.d.ts +59 -0
- package/dist/harness/tools/builtin/web-net.d.ts.map +1 -0
- package/dist/harness/tools/builtin/web-net.js +347 -0
- package/dist/harness/tools/builtin/web-net.js.map +1 -0
- package/dist/harness/tools/builtin/web-search.d.ts +40 -0
- package/dist/harness/tools/builtin/web-search.d.ts.map +1 -0
- package/dist/harness/tools/builtin/web-search.js +60 -0
- package/dist/harness/tools/builtin/web-search.js.map +1 -0
- package/dist/harness/tools/builtin/web-state.d.ts +67 -0
- package/dist/harness/tools/builtin/web-state.d.ts.map +1 -0
- package/dist/harness/tools/builtin/web-state.js +227 -0
- package/dist/harness/tools/builtin/web-state.js.map +1 -0
- package/dist/harness/tools/builtin/write-tools.d.ts +18 -0
- package/dist/harness/tools/builtin/write-tools.d.ts.map +1 -0
- package/dist/harness/tools/builtin/write-tools.js +335 -0
- package/dist/harness/tools/builtin/write-tools.js.map +1 -0
- package/dist/harness/tools/environment.d.ts +9 -0
- package/dist/harness/tools/environment.d.ts.map +1 -0
- package/dist/harness/tools/environment.js +59 -0
- package/dist/harness/tools/environment.js.map +1 -0
- package/dist/harness/tools/file-ledger.d.ts +9 -0
- package/dist/harness/tools/file-ledger.d.ts.map +1 -0
- package/dist/harness/tools/file-ledger.js +26 -0
- package/dist/harness/tools/file-ledger.js.map +1 -0
- package/dist/harness/tools/gateway.d.ts +52 -0
- package/dist/harness/tools/gateway.d.ts.map +1 -0
- package/dist/harness/tools/gateway.js +435 -0
- package/dist/harness/tools/gateway.js.map +1 -0
- package/dist/harness/tools/index.d.ts +20 -0
- package/dist/harness/tools/index.d.ts.map +1 -0
- package/dist/harness/tools/index.js +17 -0
- package/dist/harness/tools/index.js.map +1 -0
- package/dist/harness/tools/patch.d.ts +51 -0
- package/dist/harness/tools/patch.d.ts.map +1 -0
- package/dist/harness/tools/patch.js +450 -0
- package/dist/harness/tools/patch.js.map +1 -0
- package/dist/harness/tools/process.d.ts +28 -0
- package/dist/harness/tools/process.d.ts.map +1 -0
- package/dist/harness/tools/process.js +263 -0
- package/dist/harness/tools/process.js.map +1 -0
- package/dist/harness/tools/redaction.d.ts +26 -0
- package/dist/harness/tools/redaction.d.ts.map +1 -0
- package/dist/harness/tools/redaction.js +77 -0
- package/dist/harness/tools/redaction.js.map +1 -0
- package/dist/harness/tools/registry.d.ts +26 -0
- package/dist/harness/tools/registry.d.ts.map +1 -0
- package/dist/harness/tools/registry.js +57 -0
- package/dist/harness/tools/registry.js.map +1 -0
- package/dist/harness/tools/sandbox.d.ts +23 -0
- package/dist/harness/tools/sandbox.d.ts.map +1 -0
- package/dist/harness/tools/sandbox.js +117 -0
- package/dist/harness/tools/sandbox.js.map +1 -0
- package/dist/harness/tools/testing.d.ts +42 -0
- package/dist/harness/tools/testing.d.ts.map +1 -0
- package/dist/harness/tools/testing.js +143 -0
- package/dist/harness/tools/testing.js.map +1 -0
- package/dist/harness/tools/text-file.d.ts +38 -0
- package/dist/harness/tools/text-file.d.ts.map +1 -0
- package/dist/harness/tools/text-file.js +90 -0
- package/dist/harness/tools/text-file.js.map +1 -0
- package/dist/harness/tools/windows-launch.d.ts +52 -0
- package/dist/harness/tools/windows-launch.d.ts.map +1 -0
- package/dist/harness/tools/windows-launch.js +261 -0
- package/dist/harness/tools/windows-launch.js.map +1 -0
- package/dist/harness/tools/workspace-path.d.ts +34 -0
- package/dist/harness/tools/workspace-path.d.ts.map +1 -0
- package/dist/harness/tools/workspace-path.js +133 -0
- package/dist/harness/tools/workspace-path.js.map +1 -0
- package/dist/harness/tui/approvals.d.ts +44 -0
- package/dist/harness/tui/approvals.d.ts.map +1 -0
- package/dist/harness/tui/approvals.js +127 -0
- package/dist/harness/tui/approvals.js.map +1 -0
- package/dist/harness/tui/auth-interaction.d.ts +36 -0
- package/dist/harness/tui/auth-interaction.d.ts.map +1 -0
- package/dist/harness/tui/auth-interaction.js +76 -0
- package/dist/harness/tui/auth-interaction.js.map +1 -0
- package/dist/harness/tui/choice-modal.d.ts +58 -0
- package/dist/harness/tui/choice-modal.d.ts.map +1 -0
- package/dist/harness/tui/choice-modal.js +244 -0
- package/dist/harness/tui/choice-modal.js.map +1 -0
- package/dist/harness/tui/conversation-view.d.ts +250 -0
- package/dist/harness/tui/conversation-view.d.ts.map +1 -0
- package/dist/harness/tui/conversation-view.js +1115 -0
- package/dist/harness/tui/conversation-view.js.map +1 -0
- package/dist/harness/tui/describe.d.ts +21 -0
- package/dist/harness/tui/describe.d.ts.map +1 -0
- package/dist/harness/tui/describe.js +110 -0
- package/dist/harness/tui/describe.js.map +1 -0
- package/dist/harness/tui/index.d.ts +30 -0
- package/dist/harness/tui/index.d.ts.map +1 -0
- package/dist/harness/tui/index.js +26 -0
- package/dist/harness/tui/index.js.map +1 -0
- package/dist/harness/tui/input/attachments.d.ts +20 -0
- package/dist/harness/tui/input/attachments.d.ts.map +1 -0
- package/dist/harness/tui/input/attachments.js +100 -0
- package/dist/harness/tui/input/attachments.js.map +1 -0
- package/dist/harness/tui/input/autocomplete.d.ts +41 -0
- package/dist/harness/tui/input/autocomplete.d.ts.map +1 -0
- package/dist/harness/tui/input/autocomplete.js +85 -0
- package/dist/harness/tui/input/autocomplete.js.map +1 -0
- package/dist/harness/tui/input/clipboard.d.ts +44 -0
- package/dist/harness/tui/input/clipboard.d.ts.map +1 -0
- package/dist/harness/tui/input/clipboard.js +181 -0
- package/dist/harness/tui/input/clipboard.js.map +1 -0
- package/dist/harness/tui/input/commands.d.ts +23 -0
- package/dist/harness/tui/input/commands.d.ts.map +1 -0
- package/dist/harness/tui/input/commands.js +71 -0
- package/dist/harness/tui/input/commands.js.map +1 -0
- package/dist/harness/tui/input/file-index.d.ts +49 -0
- package/dist/harness/tui/input/file-index.d.ts.map +1 -0
- package/dist/harness/tui/input/file-index.js +170 -0
- package/dist/harness/tui/input/file-index.js.map +1 -0
- package/dist/harness/tui/input/index.d.ts +8 -0
- package/dist/harness/tui/input/index.d.ts.map +1 -0
- package/dist/harness/tui/input/index.js +8 -0
- package/dist/harness/tui/input/index.js.map +1 -0
- package/dist/harness/tui/input/mouse.d.ts +68 -0
- package/dist/harness/tui/input/mouse.d.ts.map +1 -0
- package/dist/harness/tui/input/mouse.js +141 -0
- package/dist/harness/tui/input/mouse.js.map +1 -0
- package/dist/harness/tui/interrupt.d.ts +25 -0
- package/dist/harness/tui/interrupt.d.ts.map +1 -0
- package/dist/harness/tui/interrupt.js +49 -0
- package/dist/harness/tui/interrupt.js.map +1 -0
- package/dist/harness/tui/jsonl-renderer.d.ts +68 -0
- package/dist/harness/tui/jsonl-renderer.d.ts.map +1 -0
- package/dist/harness/tui/jsonl-renderer.js +172 -0
- package/dist/harness/tui/jsonl-renderer.js.map +1 -0
- package/dist/harness/tui/line-source.d.ts +39 -0
- package/dist/harness/tui/line-source.d.ts.map +1 -0
- package/dist/harness/tui/line-source.js +137 -0
- package/dist/harness/tui/line-source.js.map +1 -0
- package/dist/harness/tui/logo-art.d.ts +9 -0
- package/dist/harness/tui/logo-art.d.ts.map +1 -0
- package/dist/harness/tui/logo-art.js +24 -0
- package/dist/harness/tui/logo-art.js.map +1 -0
- package/dist/harness/tui/logo.d.ts +28 -0
- package/dist/harness/tui/logo.d.ts.map +1 -0
- package/dist/harness/tui/logo.js +88 -0
- package/dist/harness/tui/logo.js.map +1 -0
- package/dist/harness/tui/open-browser.d.ts +17 -0
- package/dist/harness/tui/open-browser.d.ts.map +1 -0
- package/dist/harness/tui/open-browser.js +45 -0
- package/dist/harness/tui/open-browser.js.map +1 -0
- package/dist/harness/tui/output-chunks.d.ts +8 -0
- package/dist/harness/tui/output-chunks.d.ts.map +1 -0
- package/dist/harness/tui/output-chunks.js +48 -0
- package/dist/harness/tui/output-chunks.js.map +1 -0
- package/dist/harness/tui/panels/index.d.ts +2 -0
- package/dist/harness/tui/panels/index.d.ts.map +1 -0
- package/dist/harness/tui/panels/index.js +2 -0
- package/dist/harness/tui/panels/index.js.map +1 -0
- package/dist/harness/tui/panels/panel-view.d.ts +98 -0
- package/dist/harness/tui/panels/panel-view.d.ts.map +1 -0
- package/dist/harness/tui/panels/panel-view.js +596 -0
- package/dist/harness/tui/panels/panel-view.js.map +1 -0
- package/dist/harness/tui/pi-tui-renderer.d.ts +363 -0
- package/dist/harness/tui/pi-tui-renderer.d.ts.map +1 -0
- package/dist/harness/tui/pi-tui-renderer.js +2696 -0
- package/dist/harness/tui/pi-tui-renderer.js.map +1 -0
- package/dist/harness/tui/plain-line-renderer.d.ts +102 -0
- package/dist/harness/tui/plain-line-renderer.d.ts.map +1 -0
- package/dist/harness/tui/plain-line-renderer.js +491 -0
- package/dist/harness/tui/plain-line-renderer.js.map +1 -0
- package/dist/harness/tui/render-queue.d.ts +34 -0
- package/dist/harness/tui/render-queue.d.ts.map +1 -0
- package/dist/harness/tui/render-queue.js +92 -0
- package/dist/harness/tui/render-queue.js.map +1 -0
- package/dist/harness/tui/sanitize.d.ts +9 -0
- package/dist/harness/tui/sanitize.d.ts.map +1 -0
- package/dist/harness/tui/sanitize.js +31 -0
- package/dist/harness/tui/sanitize.js.map +1 -0
- package/dist/harness/tui/style.d.ts +54 -0
- package/dist/harness/tui/style.d.ts.map +1 -0
- package/dist/harness/tui/style.js +64 -0
- package/dist/harness/tui/style.js.map +1 -0
- package/dist/harness/tui/terminal-lifecycle.d.ts +49 -0
- package/dist/harness/tui/terminal-lifecycle.d.ts.map +1 -0
- package/dist/harness/tui/terminal-lifecycle.js +100 -0
- package/dist/harness/tui/terminal-lifecycle.js.map +1 -0
- package/dist/harness/tui/theme.d.ts +77 -0
- package/dist/harness/tui/theme.d.ts.map +1 -0
- package/dist/harness/tui/theme.js +501 -0
- package/dist/harness/tui/theme.js.map +1 -0
- package/dist/harness/tui/tool-cards.d.ts +35 -0
- package/dist/harness/tui/tool-cards.d.ts.map +1 -0
- package/dist/harness/tui/tool-cards.js +122 -0
- package/dist/harness/tui/tool-cards.js.map +1 -0
- package/dist/harness/tui/tool-row.d.ts +51 -0
- package/dist/harness/tui/tool-row.d.ts.map +1 -0
- package/dist/harness/tui/tool-row.js +98 -0
- package/dist/harness/tui/tool-row.js.map +1 -0
- package/dist/harness/tui/views/board.d.ts +49 -0
- package/dist/harness/tui/views/board.d.ts.map +1 -0
- package/dist/harness/tui/views/board.js +293 -0
- package/dist/harness/tui/views/board.js.map +1 -0
- package/dist/harness/tui/views/cards.d.ts +8 -0
- package/dist/harness/tui/views/cards.d.ts.map +1 -0
- package/dist/harness/tui/views/cards.js +206 -0
- package/dist/harness/tui/views/cards.js.map +1 -0
- package/dist/harness/tui/views/components.d.ts +101 -0
- package/dist/harness/tui/views/components.d.ts.map +1 -0
- package/dist/harness/tui/views/components.js +186 -0
- package/dist/harness/tui/views/components.js.map +1 -0
- package/dist/harness/tui/views/diff.d.ts +11 -0
- package/dist/harness/tui/views/diff.d.ts.map +1 -0
- package/dist/harness/tui/views/diff.js +150 -0
- package/dist/harness/tui/views/diff.js.map +1 -0
- package/dist/harness/tui/views/graph.d.ts +49 -0
- package/dist/harness/tui/views/graph.d.ts.map +1 -0
- package/dist/harness/tui/views/graph.js +361 -0
- package/dist/harness/tui/views/graph.js.map +1 -0
- package/dist/harness/tui/views/index.d.ts +15 -0
- package/dist/harness/tui/views/index.d.ts.map +1 -0
- package/dist/harness/tui/views/index.js +15 -0
- package/dist/harness/tui/views/index.js.map +1 -0
- package/dist/harness/tui/views/kit.d.ts +112 -0
- package/dist/harness/tui/views/kit.d.ts.map +1 -0
- package/dist/harness/tui/views/kit.js +282 -0
- package/dist/harness/tui/views/kit.js.map +1 -0
- package/dist/harness/tui/views/trust-cards.d.ts +27 -0
- package/dist/harness/tui/views/trust-cards.d.ts.map +1 -0
- package/dist/harness/tui/views/trust-cards.js +181 -0
- package/dist/harness/tui/views/trust-cards.js.map +1 -0
- package/dist/harness/tui/views/usage.d.ts +4 -0
- package/dist/harness/tui/views/usage.d.ts.map +1 -0
- package/dist/harness/tui/views/usage.js +182 -0
- package/dist/harness/tui/views/usage.js.map +1 -0
- package/dist/harness/tui/views/worker.d.ts +47 -0
- package/dist/harness/tui/views/worker.d.ts.map +1 -0
- package/dist/harness/tui/views/worker.js +221 -0
- package/dist/harness/tui/views/worker.js.map +1 -0
- package/dist/harness/tui/welcome.d.ts +97 -0
- package/dist/harness/tui/welcome.d.ts.map +1 -0
- package/dist/harness/tui/welcome.js +271 -0
- package/dist/harness/tui/welcome.js.map +1 -0
- package/dist/harness/tui/wrap.d.ts +26 -0
- package/dist/harness/tui/wrap.d.ts.map +1 -0
- package/dist/harness/tui/wrap.js +38 -0
- package/dist/harness/tui/wrap.js.map +1 -0
- package/dist/infrastructure/build-info.d.ts +5 -0
- package/dist/infrastructure/build-info.d.ts.map +1 -0
- package/dist/infrastructure/build-info.js +21 -0
- package/dist/infrastructure/build-info.js.map +1 -0
- package/dist/infrastructure/frontmatter.d.ts +25 -0
- package/dist/infrastructure/frontmatter.d.ts.map +1 -0
- package/dist/infrastructure/frontmatter.js +75 -0
- package/dist/infrastructure/frontmatter.js.map +1 -0
- package/dist/templates/agent-manifests.d.ts +19 -0
- package/dist/templates/agent-manifests.d.ts.map +1 -0
- package/dist/templates/agent-manifests.js +302 -0
- package/dist/templates/agent-manifests.js.map +1 -0
- package/dist/templates/base-skills.d.ts +23 -0
- package/dist/templates/base-skills.d.ts.map +1 -0
- package/dist/templates/base-skills.js +477 -0
- package/dist/templates/base-skills.js.map +1 -0
- package/dist/templates/skill-creator-skill.d.ts +23 -0
- package/dist/templates/skill-creator-skill.d.ts.map +1 -0
- package/dist/templates/skill-creator-skill.js +235 -0
- package/dist/templates/skill-creator-skill.js.map +1 -0
- package/dist/templates/skill-references.d.ts +13 -0
- package/dist/templates/skill-references.d.ts.map +1 -0
- package/dist/templates/skill-references.js +280 -0
- package/dist/templates/skill-references.js.map +1 -0
- package/dist/templates/structure-templates.d.ts.map +1 -1
- package/dist/templates/structure-templates.js +20 -144
- package/dist/templates/structure-templates.js.map +1 -1
- package/dist/templates/task-conductor-skill.d.ts +1 -1
- package/dist/templates/task-conductor-skill.d.ts.map +1 -1
- package/dist/templates/task-conductor-skill.js +50 -22
- package/dist/templates/task-conductor-skill.js.map +1 -1
- package/package.json +8 -2
|
@@ -0,0 +1,1357 @@
|
|
|
1
|
+
import { lstat, mkdir, readdir, readFile, readlink, realpath, rename, rm, rmdir, symlink, unlink, writeFile } from "node:fs/promises";
|
|
2
|
+
import { homedir, hostname } from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { canonicalJson, DEPENDENCY_LINK_DIRECTORIES, foldPathCase, HarnessError, isCaseInsensitivePlatform, isReservedWritePattern, pathPatternsOverlap, sameContent, sha256, staticPrefix, workspaceDigest, } from "../contracts/index.js";
|
|
5
|
+
import { GitCommandError, gitAvailable, gitCheckIgnored, gitDirtyPaths, gitHashBytes, gitHashObjects, gitHead, gitIgnoredEntries, gitIgnoredSubset, gitShowFiltered, gitSmudgeBlob, gitSubmodulePaths, gitTopLevel, gitTrackedPaths, gitTreeEntries, runGit, } from "./git.js";
|
|
6
|
+
import { findScopeViolations, isLiteralPattern, matchesAny, normalizeWorkspacePath } from "./paths.js";
|
|
7
|
+
import { contentIdentities, createWorkspaceDigestReader, resolveOnDiskPath } from "./workspace-digest.js";
|
|
8
|
+
/**
|
|
9
|
+
* Per-attempt isolation (ADR-07, ADR-19).
|
|
10
|
+
*
|
|
11
|
+
* - `worktree`: a detached git worktree at `<worktrees>/<project-hash>/<attempt-hash>` on `HEAD`
|
|
12
|
+
* (short hashed names keep Windows paths short; git runs with `core.longpaths=true`). The main
|
|
13
|
+
* workspace is untouched until `integrate`. Dirty or untracked read inputs are overlaid from the
|
|
14
|
+
* main tree and ignored dependency directories (`DEPENDENCY_LINK_DIRECTORIES`) are linked in
|
|
15
|
+
* (junction on Windows); neither is ever integrated back. A retry of the same task reuses the
|
|
16
|
+
* worktree (`IsolationCreateOptions.reuse`): it is reset to its base instead of recreated. When
|
|
17
|
+
* creating the worktree fails, a non-high-risk task falls back to `scoped-dir` with a recorded
|
|
18
|
+
* reason, and nothing is left behind.
|
|
19
|
+
* - `scoped-dir`: writes happen in place, limited by policy to `owned_paths`. A snapshot of the
|
|
20
|
+
* tree (ignored files included, but not ignored directories outside the owned paths; bounded by
|
|
21
|
+
* `SCOPED_SNAPSHOT_LIMITS`) is taken first and persisted under `<worktrees>/<project-hash>/
|
|
22
|
+
* <attempt-hash>.scoped`, so every change is seen (changed ⊆ owned), a failed attempt can be
|
|
23
|
+
* reverted, and crash recovery can revert a crashed attempt's partial writes
|
|
24
|
+
* (`pruneOrphanedAttempts`). Scoped-dir attempts of different tasks run one at a time.
|
|
25
|
+
* - `shared-read-only`: explorers and reviewers; nothing may change.
|
|
26
|
+
*
|
|
27
|
+
* Every workspace can pin its changes as an artifact: a canonical JSON document of
|
|
28
|
+
* `{path, before, after, content}` entries whose sha256 is the artifact digest a review binds to.
|
|
29
|
+
* `before`/`after` are `workspaceDigest`s in the attempt's own workspace (ADR-19). Integrate
|
|
30
|
+
* compares the main tree by `ContentIdentity` (filtered git blob id) and writes content in the main
|
|
31
|
+
* tree's representation (smudge filter, EOL), so autocrlf, `.gitattributes eol`, clean/smudge
|
|
32
|
+
* filters and Git LFS never produce a false conflict.
|
|
33
|
+
*
|
|
34
|
+
* Link safety (SEC-H4): integrate, seed and revert resolve every target segment by segment and
|
|
35
|
+
* refuse any path that traverses a symbolic link or junction, resolves elsewhere or is a
|
|
36
|
+
* multiply linked file; they never follow a link out of the workspace. Integrate also refuses a
|
|
37
|
+
* `.gitignore` change that would expose an ignored link unless the link is owned and stays inside.
|
|
38
|
+
* A worktree is always removed with its dependency links unlinked first and with Node's `rm`,
|
|
39
|
+
* which never descends into a link (`git worktree remove` would empty a junction's target).
|
|
40
|
+
*/
|
|
41
|
+
export const ARTIFACT_FORMAT = "synorch.artifact/v1";
|
|
42
|
+
export const ARTIFACT_MEDIA_TYPE = "application/vnd.synorch.artifact+json";
|
|
43
|
+
export const SCOPED_BASELINE_FORMAT = "synorch.scoped-baseline/v1";
|
|
44
|
+
export const ATTEMPT_OWNER_FORMAT = "synorch.attempt-owner/v1";
|
|
45
|
+
/** Bounds of the scoped-dir snapshot. More entries than `maxEntries` refuses scoped-dir (fail closed). */
|
|
46
|
+
export const SCOPED_SNAPSHOT_LIMITS = {
|
|
47
|
+
maxEntries: 200_000,
|
|
48
|
+
/** Files up to this size are compared by content hash; larger ones by size and mtime. */
|
|
49
|
+
hashFileBytes: 4 * 1024 * 1024,
|
|
50
|
+
/** Content hashed while taking one snapshot; files beyond it are compared by size and mtime. */
|
|
51
|
+
hashTotalBytes: 512 * 1024 * 1024,
|
|
52
|
+
/** Pre-attempt content kept on disk for revert; changes to files beyond it are detected but reported unrestorable. */
|
|
53
|
+
keepTotalBytes: 256 * 1024 * 1024,
|
|
54
|
+
};
|
|
55
|
+
/** Length of the hashed project and attempt directory names under the worktrees root. */
|
|
56
|
+
export const WORKSPACE_NAME_LENGTH = 12;
|
|
57
|
+
const DEPENDENCY_NAMES = new Set(DEPENDENCY_LINK_DIRECTORIES);
|
|
58
|
+
/**
|
|
59
|
+
* Generated output directories (P0-A): dependency installs, build output and tool caches. Outside a
|
|
60
|
+
* git root (where `git check-ignore` cannot answer) a scoped snapshot never walks them, so they never
|
|
61
|
+
* enter an artifact, its digest, the changed ⊆ owned check, a review or an integration; inside a git
|
|
62
|
+
* root the ignore rules decide instead (a tracked `build/` stays visible).
|
|
63
|
+
*/
|
|
64
|
+
export const GENERATED_DIRECTORY_NAMES = new Set([
|
|
65
|
+
...DEPENDENCY_LINK_DIRECTORIES,
|
|
66
|
+
"dist",
|
|
67
|
+
"build",
|
|
68
|
+
".next",
|
|
69
|
+
".nuxt",
|
|
70
|
+
".svelte-kit",
|
|
71
|
+
".turbo",
|
|
72
|
+
".parcel-cache",
|
|
73
|
+
".vite",
|
|
74
|
+
".cache",
|
|
75
|
+
"coverage",
|
|
76
|
+
".nyc_output",
|
|
77
|
+
"__pycache__",
|
|
78
|
+
".pytest_cache",
|
|
79
|
+
".mypy_cache",
|
|
80
|
+
".ruff_cache",
|
|
81
|
+
".gradle",
|
|
82
|
+
]);
|
|
83
|
+
/** True when an owned pattern names something at or below `directory` (its literal prefix is inside it), not merely overlaps it (`test/**` does not own `test/node_modules`). */
|
|
84
|
+
function ownsInside(ownedPaths, directory, platform) {
|
|
85
|
+
return ownedPaths.some((pattern) => {
|
|
86
|
+
const literal = [];
|
|
87
|
+
for (const segment of pattern.split("/")) {
|
|
88
|
+
if (/[*?[\]{}]/.test(segment))
|
|
89
|
+
break;
|
|
90
|
+
literal.push(segment);
|
|
91
|
+
}
|
|
92
|
+
const prefix = literal.join("/");
|
|
93
|
+
return prefix !== "" && isAtOrBelow(prefix, directory, platform);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/** True when `relative` lies inside a generated directory (`GENERATED_DIRECTORY_NAMES`) that no owned path names. */
|
|
97
|
+
export function inGeneratedDirectory(relative, ownedPaths, platform) {
|
|
98
|
+
const segments = relative.split("/");
|
|
99
|
+
for (let index = 0; index < segments.length - 1; index += 1) {
|
|
100
|
+
if (!GENERATED_DIRECTORY_NAMES.has(segments[index] ?? ""))
|
|
101
|
+
continue;
|
|
102
|
+
return !ownsInside(ownedPaths, segments.slice(0, index + 1).join("/"), platform);
|
|
103
|
+
}
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
function isolationError(code, message) {
|
|
107
|
+
return new HarnessError({ code, message: message.slice(0, 2000), workspace_effect: "none", retry_safe: code !== "policy_denied" });
|
|
108
|
+
}
|
|
109
|
+
function isMissingError(error) {
|
|
110
|
+
const code = error?.code;
|
|
111
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
112
|
+
}
|
|
113
|
+
function sameFsPath(left, right, platform) {
|
|
114
|
+
return isCaseInsensitivePlatform(platform) ? foldPathCase(left) === foldPathCase(right) : left === right;
|
|
115
|
+
}
|
|
116
|
+
function contains(root, target) {
|
|
117
|
+
const relative = path.relative(root, target);
|
|
118
|
+
return relative === "" || (relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative));
|
|
119
|
+
}
|
|
120
|
+
/** Short, stable directory name for a project or attempt id (keeps worktree paths short on Windows). */
|
|
121
|
+
export function workspaceDirectoryName(id) {
|
|
122
|
+
return sha256(id).slice("sha256:".length, "sha256:".length + WORKSPACE_NAME_LENGTH);
|
|
123
|
+
}
|
|
124
|
+
/** `<worktreesRoot>/<project-hash>`: where one project's attempt workspaces live. */
|
|
125
|
+
export function projectWorkspacesDirectory(worktreesRoot, projectId) {
|
|
126
|
+
return path.join(worktreesRoot, workspaceDirectoryName(projectId));
|
|
127
|
+
}
|
|
128
|
+
function segmentsUnder(prefix, value, platform) {
|
|
129
|
+
if (prefix.length > value.length)
|
|
130
|
+
return false;
|
|
131
|
+
return prefix.every((segment, index) => sameFsPath(segment, value[index] ?? "", platform));
|
|
132
|
+
}
|
|
133
|
+
/** True when `candidate` is `ancestor` or below it (workspace-relative, platform path policy). */
|
|
134
|
+
function isAtOrBelow(candidate, ancestor, platform) {
|
|
135
|
+
return segmentsUnder(ancestor.split("/"), candidate.split("/"), platform);
|
|
136
|
+
}
|
|
137
|
+
function workspaceRelative(relative, allowReserved) {
|
|
138
|
+
const normalized = normalizeWorkspacePath(relative);
|
|
139
|
+
if (normalized === undefined || normalized === "." || (!allowReserved && isReservedWritePattern(normalized))) {
|
|
140
|
+
throw isolationError("policy_denied", `refusing to touch ${relative}`);
|
|
141
|
+
}
|
|
142
|
+
return normalized;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The absolute path for writing `relative` under `root`. Each existing segment is checked with
|
|
146
|
+
* `lstat` and `realpath`: a symbolic link, junction or other reparse point, a non-directory
|
|
147
|
+
* parent, a directory target or a multiply linked file is refused, so the write can never land
|
|
148
|
+
* outside `root`. The on-disk spelling is kept (an NFD-created name is written as it is on disk).
|
|
149
|
+
*/
|
|
150
|
+
export async function resolveLinkSafeTarget(root, relative, platform, options = {}) {
|
|
151
|
+
const normalized = workspaceRelative(relative, options.allowReserved === true);
|
|
152
|
+
const canonicalRoot = await realpath(root);
|
|
153
|
+
const spelled = relative.replaceAll("\\", "/").split("/").filter((segment) => segment.length > 0 && segment !== ".");
|
|
154
|
+
// Keep the caller's spelling only when it differs from the workspace path by Unicode form alone.
|
|
155
|
+
const segments = spelled.join("/").normalize("NFC") === normalized ? spelled : normalized.split("/");
|
|
156
|
+
let current = canonicalRoot;
|
|
157
|
+
for (const [index, segment] of segments.entries()) {
|
|
158
|
+
current = path.join(current, segment);
|
|
159
|
+
let info;
|
|
160
|
+
try {
|
|
161
|
+
info = await lstat(current);
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
if (isMissingError(error))
|
|
165
|
+
break;
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
const last = index === segments.length - 1;
|
|
169
|
+
const at = segments.slice(0, index + 1).join("/");
|
|
170
|
+
if (info.isSymbolicLink()) {
|
|
171
|
+
if (last && options.allowFinalLink === true)
|
|
172
|
+
break;
|
|
173
|
+
throw isolationError("policy_denied", `refusing to write ${normalized}: ${at} is a symbolic link or junction`);
|
|
174
|
+
}
|
|
175
|
+
const real = await realpath(current);
|
|
176
|
+
if (!sameFsPath(real, current, platform) || !contains(canonicalRoot, real)) {
|
|
177
|
+
throw isolationError("policy_denied", `refusing to write ${normalized}: ${at} resolves elsewhere (link or reparse point)`);
|
|
178
|
+
}
|
|
179
|
+
if (!last && !info.isDirectory())
|
|
180
|
+
throw isolationError("policy_denied", `refusing to write ${normalized}: ${at} is not a directory`);
|
|
181
|
+
if (last && info.isDirectory())
|
|
182
|
+
throw isolationError("policy_denied", `refusing to write ${normalized}: it is a directory`);
|
|
183
|
+
if (last && info.isFile() && info.nlink > 1)
|
|
184
|
+
throw isolationError("policy_denied", `refusing to write ${normalized}: it has more than one hard link`);
|
|
185
|
+
}
|
|
186
|
+
return path.join(canonicalRoot, ...segments);
|
|
187
|
+
}
|
|
188
|
+
async function writeSafe(root, relative, bytes, platform, options = {}) {
|
|
189
|
+
const first = await resolveLinkSafeTarget(root, relative, platform, options);
|
|
190
|
+
await mkdir(path.dirname(first), { recursive: true });
|
|
191
|
+
// Re-resolve after creating parents: a link planted meanwhile is refused before any byte is written.
|
|
192
|
+
const target = await resolveLinkSafeTarget(root, relative, platform, options);
|
|
193
|
+
const temporary = `${target}.synorch-${process.pid}-${Date.now()}.tmp`;
|
|
194
|
+
await writeFile(temporary, bytes, { flag: "wx" });
|
|
195
|
+
try {
|
|
196
|
+
await renameWithRetry(temporary, target);
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
await unlink(temporary).catch(() => undefined);
|
|
200
|
+
throw error;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const RETRYABLE_RENAME_CODES = new Set(["EPERM", "EACCES", "EBUSY"]);
|
|
204
|
+
/** Windows: a rename over a file another process briefly holds open fails transiently; retry with a bounded backoff. */
|
|
205
|
+
async function renameWithRetry(from, to) {
|
|
206
|
+
for (let attempt = 1;; attempt += 1) {
|
|
207
|
+
try {
|
|
208
|
+
await rename(from, to);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
const code = error instanceof Error && "code" in error ? String(error.code) : "";
|
|
213
|
+
if (attempt >= 8 || !RETRYABLE_RENAME_CODES.has(code))
|
|
214
|
+
throw error;
|
|
215
|
+
await new Promise((resolve) => setTimeout(resolve, 5 * 2 ** attempt));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
async function removeSafe(root, relative, platform, options = {}) {
|
|
220
|
+
const target = await resolveLinkSafeTarget(root, relative, platform, { ...options, allowFinalLink: true });
|
|
221
|
+
try {
|
|
222
|
+
await unlink(target);
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
if (!isMissingError(error))
|
|
226
|
+
throw error;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/** Removes a symbolic link or junction itself, never what it points to; anything else is left alone. */
|
|
230
|
+
async function unlinkLink(file) {
|
|
231
|
+
let info;
|
|
232
|
+
try {
|
|
233
|
+
info = await lstat(file);
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (!info.isSymbolicLink())
|
|
239
|
+
return;
|
|
240
|
+
try {
|
|
241
|
+
await unlink(file);
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
// A directory junction on some Windows versions only yields to rmdir, which removes the reparse point, not the target.
|
|
245
|
+
await rmdir(file).catch(() => undefined);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/** What `relative` is under `root`, without following any link (a link on the way is reported as a link). */
|
|
249
|
+
async function observe(root, relative) {
|
|
250
|
+
const segments = relative.split("/");
|
|
251
|
+
let current = root;
|
|
252
|
+
for (const [index, segment] of segments.entries()) {
|
|
253
|
+
current = path.join(current, segment);
|
|
254
|
+
let info;
|
|
255
|
+
try {
|
|
256
|
+
info = await lstat(current);
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
return { kind: "absent" };
|
|
260
|
+
}
|
|
261
|
+
const last = index === segments.length - 1;
|
|
262
|
+
if (info.isSymbolicLink()) {
|
|
263
|
+
return { kind: "link", target: last ? await readlink(current).catch(() => "") : `through ${segments.slice(0, index + 1).join("/")}` };
|
|
264
|
+
}
|
|
265
|
+
if (!last) {
|
|
266
|
+
if (!info.isDirectory())
|
|
267
|
+
return { kind: "absent" };
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
if (info.isFile())
|
|
271
|
+
return { kind: "file", bytes: await readFile(current) };
|
|
272
|
+
return info.isDirectory() ? { kind: "absent" } : { kind: "other" };
|
|
273
|
+
}
|
|
274
|
+
return { kind: "absent" };
|
|
275
|
+
}
|
|
276
|
+
function observedDigest(observed) {
|
|
277
|
+
switch (observed.kind) {
|
|
278
|
+
case "absent":
|
|
279
|
+
return null;
|
|
280
|
+
case "file":
|
|
281
|
+
return workspaceDigest(observed.bytes);
|
|
282
|
+
case "link":
|
|
283
|
+
return sha256(`link:${observed.target}`);
|
|
284
|
+
case "other":
|
|
285
|
+
return sha256("other");
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
async function writeJsonAtomic(file, value) {
|
|
289
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
290
|
+
const temporary = `${file}.${process.pid}-${Date.now()}.tmp`;
|
|
291
|
+
await writeFile(temporary, `${JSON.stringify(value)}\n`, { mode: 0o600 });
|
|
292
|
+
await renameWithRetry(temporary, file);
|
|
293
|
+
}
|
|
294
|
+
async function readJson(file) {
|
|
295
|
+
try {
|
|
296
|
+
return JSON.parse(await readFile(file, "utf8"));
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
return undefined;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
async function samePath(left, right, platform) {
|
|
303
|
+
try {
|
|
304
|
+
return sameFsPath(await realpath(left), await realpath(right), platform);
|
|
305
|
+
}
|
|
306
|
+
catch {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
function encodeArtifact(mode, baseCommit, changes, contents) {
|
|
311
|
+
const document = {
|
|
312
|
+
format: ARTIFACT_FORMAT,
|
|
313
|
+
mode,
|
|
314
|
+
base_commit: baseCommit ?? null,
|
|
315
|
+
changes: changes.map((change) => {
|
|
316
|
+
const content = contents.get(change.path);
|
|
317
|
+
return {
|
|
318
|
+
path: change.path,
|
|
319
|
+
before: change.before,
|
|
320
|
+
after: change.after,
|
|
321
|
+
content: content === null || content === undefined ? null : content.toString("base64"),
|
|
322
|
+
};
|
|
323
|
+
}),
|
|
324
|
+
};
|
|
325
|
+
return Buffer.from(canonicalJson(document), "utf8");
|
|
326
|
+
}
|
|
327
|
+
export function decodeArtifact(bytes) {
|
|
328
|
+
const parsed = JSON.parse(Buffer.from(bytes).toString("utf8"));
|
|
329
|
+
if (parsed.format !== ARTIFACT_FORMAT || !Array.isArray(parsed.changes)) {
|
|
330
|
+
throw isolationError("verification_failed", "not a synorch artifact");
|
|
331
|
+
}
|
|
332
|
+
return parsed;
|
|
333
|
+
}
|
|
334
|
+
async function walkTree(root, limit, options = {}) {
|
|
335
|
+
const entries = [];
|
|
336
|
+
const visit = async (relative, filter) => {
|
|
337
|
+
const directory = relative === "" ? root : path.join(root, ...relative.split("/"));
|
|
338
|
+
let children;
|
|
339
|
+
try {
|
|
340
|
+
children = await readdir(directory, { withFileTypes: true });
|
|
341
|
+
}
|
|
342
|
+
catch {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
for (const child of children) {
|
|
346
|
+
if (filter !== undefined && !filter(child.name))
|
|
347
|
+
continue;
|
|
348
|
+
const childRelative = relative === "" ? child.name : `${relative}/${child.name}`;
|
|
349
|
+
if (child.isSymbolicLink())
|
|
350
|
+
entries.push({ relative: childRelative, kind: "link" });
|
|
351
|
+
else if (child.isDirectory()) {
|
|
352
|
+
if (options.excluded !== undefined && sameFsPath(path.resolve(directory, child.name), path.resolve(options.excluded), process.platform))
|
|
353
|
+
continue;
|
|
354
|
+
if (options.watchGitConfig === true && relative === "" && child.name === ".git") {
|
|
355
|
+
await visit(childRelative, (name) => name === "config" || name === "hooks");
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
if (options.skipDirectory?.(childRelative, child.name) === true)
|
|
359
|
+
continue;
|
|
360
|
+
await visit(childRelative);
|
|
361
|
+
}
|
|
362
|
+
else if (child.isFile())
|
|
363
|
+
entries.push({ relative: childRelative, kind: "file" });
|
|
364
|
+
if (entries.length > limit) {
|
|
365
|
+
throw isolationError("sandbox_insufficient", `the workspace has more than ${limit} entries; a scoped-dir snapshot would be unbounded (commit your changes so the attempt can use a worktree)`);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
await visit("");
|
|
370
|
+
return entries;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Directories a scoped snapshot never walks: `.git` (except config/hooks), the ignored directories
|
|
374
|
+
* git reports (`skipDirs`) and generated directories by name (dependency directories in a git root,
|
|
375
|
+
* `GENERATED_DIRECTORY_NAMES` outside one), unless an owned path names something inside them.
|
|
376
|
+
*/
|
|
377
|
+
function scopedSkipRule(skipDirs, ownedPaths, platform, gitRoot) {
|
|
378
|
+
const key = (value) => (isCaseInsensitivePlatform(platform) ? foldPathCase(value) : value);
|
|
379
|
+
const skipped = new Set(skipDirs.map(key));
|
|
380
|
+
const names = gitRoot ? DEPENDENCY_NAMES : GENERATED_DIRECTORY_NAMES;
|
|
381
|
+
return (relative, name) => {
|
|
382
|
+
if (name === ".git")
|
|
383
|
+
return true;
|
|
384
|
+
if (skipped.has(key(relative)))
|
|
385
|
+
return true;
|
|
386
|
+
return names.has(name) && !ownsInside(ownedPaths, relative, platform);
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
async function sign(root, entry, method, budget) {
|
|
390
|
+
const file = path.join(root, ...entry.relative.split("/"));
|
|
391
|
+
try {
|
|
392
|
+
if (entry.kind === "link") {
|
|
393
|
+
const signature = `link:${await readlink(file)}`;
|
|
394
|
+
return { method: "l", signature, digest: sha256(signature), bytes: undefined };
|
|
395
|
+
}
|
|
396
|
+
const info = await lstat(file);
|
|
397
|
+
if (!info.isFile())
|
|
398
|
+
return undefined;
|
|
399
|
+
const hash = method === "h" ||
|
|
400
|
+
(method === undefined &&
|
|
401
|
+
info.size <= SCOPED_SNAPSHOT_LIMITS.hashFileBytes &&
|
|
402
|
+
(budget === undefined || budget.hashed + info.size <= SCOPED_SNAPSHOT_LIMITS.hashTotalBytes));
|
|
403
|
+
if (hash) {
|
|
404
|
+
const bytes = await readFile(file);
|
|
405
|
+
if (budget !== undefined)
|
|
406
|
+
budget.hashed += bytes.byteLength;
|
|
407
|
+
const digest = workspaceDigest(bytes);
|
|
408
|
+
return { method: "h", signature: digest, digest, bytes };
|
|
409
|
+
}
|
|
410
|
+
const signature = `stat:${info.size}:${info.mtimeMs}`;
|
|
411
|
+
return { method: "s", signature, digest: sha256(signature), bytes: undefined };
|
|
412
|
+
}
|
|
413
|
+
catch {
|
|
414
|
+
return undefined;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
const SCOPED_STATE_FILE = "state.json";
|
|
418
|
+
const SCOPED_OWNER_FILE = "owner.json";
|
|
419
|
+
const SCOPED_MANIFEST_FILE = "manifest.json";
|
|
420
|
+
class ScopedSnapshot {
|
|
421
|
+
root;
|
|
422
|
+
directory;
|
|
423
|
+
records;
|
|
424
|
+
git;
|
|
425
|
+
skipDirs;
|
|
426
|
+
platform;
|
|
427
|
+
ownedPaths;
|
|
428
|
+
constructor(root, directory, records, git, ownedPaths, skipDirs, platform) {
|
|
429
|
+
this.root = root;
|
|
430
|
+
this.directory = directory;
|
|
431
|
+
this.records = records;
|
|
432
|
+
this.git = git;
|
|
433
|
+
this.ownedPaths = ownedPaths;
|
|
434
|
+
this.skipDirs = skipDirs;
|
|
435
|
+
this.platform = platform;
|
|
436
|
+
}
|
|
437
|
+
walk() {
|
|
438
|
+
return walkTree(this.root, SCOPED_SNAPSHOT_LIMITS.maxEntries, {
|
|
439
|
+
excluded: path.dirname(this.directory),
|
|
440
|
+
watchGitConfig: true,
|
|
441
|
+
skipDirectory: scopedSkipRule(this.skipDirs, this.ownedPaths, this.platform, this.git !== undefined),
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
static async take(options) {
|
|
445
|
+
const snapshot = new ScopedSnapshot(options.root, options.directory, new Map(), options.git, options.ownedPaths, options.skipDirs, options.platform);
|
|
446
|
+
const entries = await snapshot.walk();
|
|
447
|
+
await rm(options.directory, { recursive: true, force: true });
|
|
448
|
+
await mkdir(path.join(options.directory, "blobs"), { recursive: true, mode: 0o700 });
|
|
449
|
+
await writeJsonAtomic(path.join(options.directory, SCOPED_OWNER_FILE), options.owner);
|
|
450
|
+
await writeJsonAtomic(path.join(options.directory, SCOPED_STATE_FILE), { state: "active" });
|
|
451
|
+
const budget = { hashed: 0 };
|
|
452
|
+
let keptBytes = 0;
|
|
453
|
+
const records = new Map();
|
|
454
|
+
const written = new Set();
|
|
455
|
+
for (const entry of entries) {
|
|
456
|
+
const signed = await sign(options.root, entry, undefined, budget);
|
|
457
|
+
if (signed === undefined)
|
|
458
|
+
continue;
|
|
459
|
+
let kept = false;
|
|
460
|
+
const fromGit = options.git !== undefined && options.restorableFromGit?.has(entry.relative) === true;
|
|
461
|
+
if (!fromGit && signed.bytes !== undefined && keptBytes + signed.bytes.byteLength <= SCOPED_SNAPSHOT_LIMITS.keepTotalBytes) {
|
|
462
|
+
const name = signed.digest.slice("sha256:".length);
|
|
463
|
+
if (!written.has(name)) {
|
|
464
|
+
await writeFile(path.join(options.directory, "blobs", name), signed.bytes, { mode: 0o600 });
|
|
465
|
+
written.add(name);
|
|
466
|
+
keptBytes += signed.bytes.byteLength;
|
|
467
|
+
}
|
|
468
|
+
kept = true;
|
|
469
|
+
}
|
|
470
|
+
records.set(entry.relative, { method: signed.method, signature: signed.signature, digest: signed.digest, kept });
|
|
471
|
+
}
|
|
472
|
+
const manifest = {
|
|
473
|
+
format: SCOPED_BASELINE_FORMAT,
|
|
474
|
+
attempt_id: options.owner.attempt_id,
|
|
475
|
+
workspace_root: options.root,
|
|
476
|
+
owned_paths: options.ownedPaths,
|
|
477
|
+
git_head: options.git?.head ?? null,
|
|
478
|
+
created_at: new Date().toISOString(),
|
|
479
|
+
skip_dirs: options.skipDirs,
|
|
480
|
+
entries: Object.fromEntries([...records].map(([relative, record]) => [relative, [record.method, record.signature, record.digest, record.kept]])),
|
|
481
|
+
};
|
|
482
|
+
await writeJsonAtomic(path.join(options.directory, SCOPED_MANIFEST_FILE), manifest);
|
|
483
|
+
return new ScopedSnapshot(options.root, options.directory, records, options.git, options.ownedPaths, options.skipDirs, options.platform);
|
|
484
|
+
}
|
|
485
|
+
/** Reloads a persisted snapshot (crash recovery); undefined when it is missing or unreadable. */
|
|
486
|
+
static async load(directory, git, platform) {
|
|
487
|
+
const manifest = (await readJson(path.join(directory, SCOPED_MANIFEST_FILE)));
|
|
488
|
+
if (manifest?.format !== SCOPED_BASELINE_FORMAT || typeof manifest.workspace_root !== "string" || manifest.entries === undefined)
|
|
489
|
+
return undefined;
|
|
490
|
+
const records = new Map();
|
|
491
|
+
for (const [relative, value] of Object.entries(manifest.entries)) {
|
|
492
|
+
const [method, signature, digest, kept] = value;
|
|
493
|
+
records.set(relative, { method, signature, digest: digest, kept });
|
|
494
|
+
}
|
|
495
|
+
const head = typeof manifest.git_head === "string" ? { runner: git, head: manifest.git_head } : undefined;
|
|
496
|
+
return new ScopedSnapshot(manifest.workspace_root, directory, records, head, manifest.owned_paths ?? [], manifest.skip_dirs ?? [], platform);
|
|
497
|
+
}
|
|
498
|
+
get workspaceRoot() {
|
|
499
|
+
return this.root;
|
|
500
|
+
}
|
|
501
|
+
async beforeDigest(relative) {
|
|
502
|
+
return this.records.get(relative)?.digest ?? null;
|
|
503
|
+
}
|
|
504
|
+
async beforeContent(relative) {
|
|
505
|
+
const record = this.records.get(relative);
|
|
506
|
+
if (record === undefined)
|
|
507
|
+
return null;
|
|
508
|
+
if (record.method === "l")
|
|
509
|
+
return undefined;
|
|
510
|
+
if (record.kept) {
|
|
511
|
+
const bytes = await readFile(path.join(this.directory, "blobs", record.digest.slice("sha256:".length))).catch(() => undefined);
|
|
512
|
+
if (bytes !== undefined && workspaceDigest(bytes) === record.digest)
|
|
513
|
+
return bytes;
|
|
514
|
+
}
|
|
515
|
+
if (this.git !== undefined && record.method === "h") {
|
|
516
|
+
// The checkout form (smudge + EOL) of the base blob; used only when it is byte-identical to the snapshot.
|
|
517
|
+
const bytes = await gitShowFiltered(this.git.runner, this.root, this.git.head, relative);
|
|
518
|
+
if (bytes !== undefined && workspaceDigest(bytes) === record.digest)
|
|
519
|
+
return bytes;
|
|
520
|
+
}
|
|
521
|
+
return undefined;
|
|
522
|
+
}
|
|
523
|
+
async candidates() {
|
|
524
|
+
const now = await this.walk();
|
|
525
|
+
const seen = new Set();
|
|
526
|
+
const changed = [];
|
|
527
|
+
for (const entry of now) {
|
|
528
|
+
seen.add(entry.relative);
|
|
529
|
+
const record = this.records.get(entry.relative);
|
|
530
|
+
const method = record === undefined ? undefined : entry.kind === "link" ? "l" : record.method === "l" ? undefined : record.method;
|
|
531
|
+
const signed = await sign(this.root, entry, method, undefined);
|
|
532
|
+
if (signed === undefined)
|
|
533
|
+
continue;
|
|
534
|
+
if (record === undefined || record.signature !== signed.signature)
|
|
535
|
+
changed.push(entry.relative);
|
|
536
|
+
}
|
|
537
|
+
for (const relative of this.records.keys())
|
|
538
|
+
if (!seen.has(relative))
|
|
539
|
+
changed.push(relative);
|
|
540
|
+
if (this.git === undefined)
|
|
541
|
+
return changed;
|
|
542
|
+
// New entries git ignores (build output, caches created during the attempt) are generated, not
|
|
543
|
+
// part of the artifact; an owned literal file is always kept (B8).
|
|
544
|
+
const fresh = changed.filter((relative) => !this.records.has(relative) && !this.ownedPaths.some((pattern) => isLiteralPattern(pattern) && pattern === relative));
|
|
545
|
+
// An ignored write outside the owned paths (a stray `.env`) stays visible so integrate refuses it (SEC-M1).
|
|
546
|
+
const ignored = await gitIgnoredSubset(this.git.runner, this.root, fresh).catch(() => new Set());
|
|
547
|
+
const generated = new Set(fresh.filter((relative) => inGeneratedDirectory(relative, this.ownedPaths, this.platform) || (ignored.has(relative) && matchesAny(relative, this.ownedPaths, this.platform))));
|
|
548
|
+
return generated.size === 0 ? changed : changed.filter((relative) => !generated.has(relative));
|
|
549
|
+
}
|
|
550
|
+
async markIntegrated() {
|
|
551
|
+
await writeJsonAtomic(path.join(this.directory, SCOPED_STATE_FILE), { state: "integrated" }).catch(() => undefined);
|
|
552
|
+
}
|
|
553
|
+
async destroy() {
|
|
554
|
+
await rm(this.directory, { recursive: true, force: true }).catch(() => undefined);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
/** Restores `paths` (as named on disk) from `baseline`, link-safely; a path that cannot be restored is reported, never forced. */
|
|
558
|
+
async function restorePaths(root, baseline, paths, platform) {
|
|
559
|
+
const restored = [];
|
|
560
|
+
const unrestorable = [];
|
|
561
|
+
for (const relative of paths) {
|
|
562
|
+
try {
|
|
563
|
+
const original = await baseline.beforeContent(relative);
|
|
564
|
+
if (original === undefined) {
|
|
565
|
+
unrestorable.push(relative);
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
if (original === null) {
|
|
569
|
+
await removeSafe(root, relative, platform, { allowReserved: true });
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
572
|
+
if ((await observe(root, relative)).kind === "link")
|
|
573
|
+
await removeSafe(root, relative, platform, { allowReserved: true });
|
|
574
|
+
await writeSafe(root, relative, original, platform, { allowReserved: true });
|
|
575
|
+
}
|
|
576
|
+
restored.push(relative);
|
|
577
|
+
}
|
|
578
|
+
catch (error) {
|
|
579
|
+
if (error instanceof HarnessError && error.info.code === "policy_denied") {
|
|
580
|
+
unrestorable.push(relative);
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
throw error;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return { restored, unrestorable };
|
|
587
|
+
}
|
|
588
|
+
function eolStyle(bytes) {
|
|
589
|
+
let lf = 0;
|
|
590
|
+
let crlf = 0;
|
|
591
|
+
for (let index = 0; index < bytes.length; index += 1) {
|
|
592
|
+
const byte = bytes[index];
|
|
593
|
+
if (byte === 0x0a) {
|
|
594
|
+
if (index > 0 && bytes[index - 1] === 0x0d)
|
|
595
|
+
crlf += 1;
|
|
596
|
+
else
|
|
597
|
+
lf += 1;
|
|
598
|
+
}
|
|
599
|
+
else if (byte === 0x0d && bytes[index + 1] !== 0x0a)
|
|
600
|
+
return "mixed";
|
|
601
|
+
}
|
|
602
|
+
if (lf === 0 && crlf === 0)
|
|
603
|
+
return "none";
|
|
604
|
+
if (lf > 0 && crlf > 0)
|
|
605
|
+
return "mixed";
|
|
606
|
+
return lf > 0 ? "lf" : "crlf";
|
|
607
|
+
}
|
|
608
|
+
function withEol(bytes, style) {
|
|
609
|
+
const lf = Buffer.from(bytes.toString("latin1").replace(/\r\n/g, "\n"), "latin1");
|
|
610
|
+
return style === "lf" ? lf : Buffer.from(lf.toString("latin1").replace(/\n/g, "\r\n"), "latin1");
|
|
611
|
+
}
|
|
612
|
+
// ---------------------------------------------------------------------------------------------
|
|
613
|
+
// Worktree failures (B7)
|
|
614
|
+
// ---------------------------------------------------------------------------------------------
|
|
615
|
+
function classifyWorktreeFailure(error) {
|
|
616
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
617
|
+
const detail = message.replace(/\s+/g, " ").trim().slice(0, 500);
|
|
618
|
+
if (error instanceof GitCommandError && error.spawnCode !== undefined)
|
|
619
|
+
return { reason: "git-unavailable", detail };
|
|
620
|
+
if (/filename too long|file name too long|too long|ENAMETOOLONG|too big|path.*exceeds/i.test(message))
|
|
621
|
+
return { reason: "path-too-long", detail };
|
|
622
|
+
return { reason: "worktree-create-failed", detail };
|
|
623
|
+
}
|
|
624
|
+
function abortable(signal) {
|
|
625
|
+
return new Promise((_, reject) => {
|
|
626
|
+
if (signal.aborted)
|
|
627
|
+
reject(isolationError("internal", "cancelled while waiting for a scoped-dir slot"));
|
|
628
|
+
signal.addEventListener("abort", () => reject(isolationError("internal", "cancelled while waiting for a scoped-dir slot")), { once: true });
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
export function createIsolationProvider(deps) {
|
|
632
|
+
const git = deps.git ?? runGit;
|
|
633
|
+
const platform = deps.platform ?? process.platform;
|
|
634
|
+
const home = deps.home ?? homedir();
|
|
635
|
+
const slots = new Set();
|
|
636
|
+
const slotOf = new Map();
|
|
637
|
+
const scopedSnapshots = new Map();
|
|
638
|
+
const leases = new Map();
|
|
639
|
+
/** Main-tree digests of paths this provider integrated; such dirty paths are ours, not the user's. */
|
|
640
|
+
const integrated = new Map();
|
|
641
|
+
const worktreesRoot = deps.worktreesRoot ?? path.join(home, ".synorch", "worktrees");
|
|
642
|
+
const projectDirectory = projectWorkspacesDirectory(worktreesRoot, deps.projectId);
|
|
643
|
+
const worktreePath = (attemptId) => path.join(projectDirectory, workspaceDirectoryName(attemptId));
|
|
644
|
+
const ownerFile = (attemptId) => `${worktreePath(attemptId)}.owner.json`;
|
|
645
|
+
const scopedDirectory = (attemptId) => `${worktreePath(attemptId)}.scoped`;
|
|
646
|
+
const ownerOf = (attemptId, mode, links = []) => ({
|
|
647
|
+
format: ATTEMPT_OWNER_FORMAT,
|
|
648
|
+
attempt_id: attemptId,
|
|
649
|
+
mode,
|
|
650
|
+
pid: process.pid,
|
|
651
|
+
host: hostname(),
|
|
652
|
+
workspace_root: deps.workspaceRoot,
|
|
653
|
+
created_at: new Date().toISOString(),
|
|
654
|
+
...(links.length > 0 ? { dependency_links: links } : {}),
|
|
655
|
+
});
|
|
656
|
+
const acquireSlot = async (taskId, signal) => {
|
|
657
|
+
for (;;) {
|
|
658
|
+
const blocking = [...slots].filter((slot) => slot.taskId !== taskId);
|
|
659
|
+
if (blocking.length === 0)
|
|
660
|
+
break;
|
|
661
|
+
await Promise.race([...blocking.map((slot) => slot.released), abortable(signal)]);
|
|
662
|
+
}
|
|
663
|
+
let release;
|
|
664
|
+
const released = new Promise((resolve) => {
|
|
665
|
+
release = resolve;
|
|
666
|
+
});
|
|
667
|
+
const slot = {
|
|
668
|
+
taskId,
|
|
669
|
+
released,
|
|
670
|
+
release: () => {
|
|
671
|
+
slots.delete(slot);
|
|
672
|
+
release();
|
|
673
|
+
},
|
|
674
|
+
foreign: new Map(),
|
|
675
|
+
};
|
|
676
|
+
slots.add(slot);
|
|
677
|
+
return slot;
|
|
678
|
+
};
|
|
679
|
+
const pinned = async (workspace, baseline, signal) => {
|
|
680
|
+
const changes = [];
|
|
681
|
+
const contents = new Map();
|
|
682
|
+
const unsafe = [];
|
|
683
|
+
const unsafeReasons = new Map();
|
|
684
|
+
const diskPaths = new Map();
|
|
685
|
+
if (baseline !== undefined) {
|
|
686
|
+
const foreign = slotOf.get(workspace.self())?.foreign;
|
|
687
|
+
const candidates = [...new Set(await baseline.candidates(signal))].sort();
|
|
688
|
+
const flagged = baseline.flagged?.() ?? new Map();
|
|
689
|
+
const byPath = new Map();
|
|
690
|
+
for (const candidate of candidates) {
|
|
691
|
+
const normalized = normalizeWorkspacePath(candidate);
|
|
692
|
+
if (normalized === undefined) {
|
|
693
|
+
// Not expressible as a workspace path: never silently dropped from changed ⊆ owned.
|
|
694
|
+
unsafe.push(candidate);
|
|
695
|
+
continue;
|
|
696
|
+
}
|
|
697
|
+
byPath.set(normalized, [...(byPath.get(normalized) ?? []), candidate]);
|
|
698
|
+
}
|
|
699
|
+
for (const [normalized, spellings] of [...byPath].sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))) {
|
|
700
|
+
const changed = [];
|
|
701
|
+
for (const onDisk of spellings) {
|
|
702
|
+
const before = await baseline.beforeDigest(onDisk);
|
|
703
|
+
const observed = await observe(workspace.root, onDisk);
|
|
704
|
+
const after = observedDigest(observed);
|
|
705
|
+
if (before !== after)
|
|
706
|
+
changed.push({ onDisk, before, after, observed });
|
|
707
|
+
}
|
|
708
|
+
const first = changed[0];
|
|
709
|
+
if (first === undefined)
|
|
710
|
+
continue;
|
|
711
|
+
if (foreign !== undefined && changed.length === 1 && foreign.has(normalized) && foreign.get(normalized) === first.after)
|
|
712
|
+
continue;
|
|
713
|
+
if (changed.length > 1) {
|
|
714
|
+
unsafe.push(normalized);
|
|
715
|
+
unsafeReasons.set(normalized, `${changed.length} files differ only in Unicode normalization (${changed.map((entry) => JSON.stringify(entry.onDisk)).join(", ")})`);
|
|
716
|
+
}
|
|
717
|
+
else if (flagged.has(first.onDisk)) {
|
|
718
|
+
unsafe.push(normalized);
|
|
719
|
+
unsafeReasons.set(normalized, flagged.get(first.onDisk) ?? "cannot be integrated");
|
|
720
|
+
}
|
|
721
|
+
else if (first.observed.kind === "link" || first.observed.kind === "other")
|
|
722
|
+
unsafe.push(normalized);
|
|
723
|
+
changes.push({ path: normalized, before: first.before, after: first.after });
|
|
724
|
+
contents.set(normalized, first.observed.kind === "file" ? first.observed.bytes : null);
|
|
725
|
+
if (first.onDisk !== normalized)
|
|
726
|
+
diskPaths.set(normalized, first.onDisk);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
const artifactBytes = encodeArtifact(workspace.mode, workspace.baseCommit, changes, contents);
|
|
730
|
+
const artifactDigest = sha256(artifactBytes);
|
|
731
|
+
if (deps.blobs !== undefined)
|
|
732
|
+
await deps.blobs.put(artifactBytes, ARTIFACT_MEDIA_TYPE);
|
|
733
|
+
return {
|
|
734
|
+
artifactDigest,
|
|
735
|
+
artifactBytes,
|
|
736
|
+
changes,
|
|
737
|
+
contents,
|
|
738
|
+
unsafe,
|
|
739
|
+
...(unsafeReasons.size > 0 ? { unsafeReasons } : {}),
|
|
740
|
+
...(diskPaths.size > 0 ? { diskPaths } : {}),
|
|
741
|
+
};
|
|
742
|
+
};
|
|
743
|
+
const build = (attemptId, packet, mode, root, baseCommit, baseline, cleanup, extras = {}, slot) => {
|
|
744
|
+
let disposed = false;
|
|
745
|
+
const workspace = {
|
|
746
|
+
attemptId,
|
|
747
|
+
mode,
|
|
748
|
+
root,
|
|
749
|
+
baseCommit,
|
|
750
|
+
ownedPaths: packet.scope.owned_paths,
|
|
751
|
+
forbiddenPaths: packet.scope.forbidden_paths,
|
|
752
|
+
digest: createWorkspaceDigestReader(root, { platform }),
|
|
753
|
+
...extras,
|
|
754
|
+
changeSet: (signal) => pinned({ mode, root, baseCommit, self: () => workspace }, baseline, signal),
|
|
755
|
+
async snapshot(signal) {
|
|
756
|
+
const set = await workspace.changeSet(signal);
|
|
757
|
+
return { artifactDigest: set.artifactDigest, changedPaths: set.changes.map((change) => change.path) };
|
|
758
|
+
},
|
|
759
|
+
async revert(signal) {
|
|
760
|
+
if (baseline === undefined)
|
|
761
|
+
return [];
|
|
762
|
+
const set = await workspace.changeSet(signal);
|
|
763
|
+
// Every change is restored, owned or not (SEC-M1), except a declared input the task only
|
|
764
|
+
// reads: its change is a source change the freshness check attributes to the user, and the
|
|
765
|
+
// re-packaged attempt must see it.
|
|
766
|
+
const sources = packet.context.sources.map((source) => source.path);
|
|
767
|
+
// The workspace-wide read pattern (`**`) is a read grant, not a declared input: it never exempts a change.
|
|
768
|
+
const inputs = [...packet.scope.read_paths.filter((pattern) => pattern !== "**"), ...sources];
|
|
769
|
+
const paths = set.changes
|
|
770
|
+
.map((change) => change.path)
|
|
771
|
+
.filter((relative) => matchesAny(relative, packet.scope.owned_paths, platform) || !matchesAny(relative, inputs, platform));
|
|
772
|
+
const onDisk = paths.map((relative) => set.diskPaths?.get(relative) ?? relative);
|
|
773
|
+
const outcome = await restorePaths(root, baseline, onDisk, platform);
|
|
774
|
+
const back = new Map(paths.map((relative, index) => [onDisk[index] ?? relative, relative]));
|
|
775
|
+
return outcome.restored.map((relative) => back.get(relative) ?? relative);
|
|
776
|
+
},
|
|
777
|
+
async dispose() {
|
|
778
|
+
if (disposed)
|
|
779
|
+
return;
|
|
780
|
+
disposed = true;
|
|
781
|
+
slotOf.delete(workspace);
|
|
782
|
+
try {
|
|
783
|
+
await cleanup(workspace);
|
|
784
|
+
}
|
|
785
|
+
finally {
|
|
786
|
+
slot?.release();
|
|
787
|
+
}
|
|
788
|
+
},
|
|
789
|
+
};
|
|
790
|
+
if (slot !== undefined)
|
|
791
|
+
slotOf.set(workspace, slot);
|
|
792
|
+
return workspace;
|
|
793
|
+
};
|
|
794
|
+
/** Deletes a worktree without ever descending into a link: links first, then Node's `rm`, then `worktree prune`. */
|
|
795
|
+
const discardLease = async (lease) => {
|
|
796
|
+
lease.closed = true;
|
|
797
|
+
for (const link of lease.links)
|
|
798
|
+
await unlinkLink(path.join(lease.target, ...link.split("/")));
|
|
799
|
+
await rm(lease.target, { recursive: true, force: true, maxRetries: 3 }).catch(() => undefined);
|
|
800
|
+
await git(["worktree", "prune"], deps.workspaceRoot).catch(() => undefined);
|
|
801
|
+
await rm(ownerFile(lease.ownerAttemptId), { force: true }).catch(() => undefined);
|
|
802
|
+
};
|
|
803
|
+
/** Removes everything untracked or ignored in a worktree (like `git clean -fdx`) with Node's `rm`, which never follows a junction. */
|
|
804
|
+
const cleanUntracked = async (target, signal) => {
|
|
805
|
+
const output = (await git(["ls-files", "--others", "--directory", "-z"], target, signal)).stdout.toString("utf8");
|
|
806
|
+
for (const entry of output.split("\0").filter((value) => value.length > 0)) {
|
|
807
|
+
const relative = entry.replace(/\/+$/, "");
|
|
808
|
+
if (relative.length === 0 || relative.split("/").includes(".."))
|
|
809
|
+
continue;
|
|
810
|
+
await rm(path.join(target, ...relative.split("/")), { recursive: true, force: true, maxRetries: 3 });
|
|
811
|
+
}
|
|
812
|
+
};
|
|
813
|
+
/**
|
|
814
|
+
* Fills a fresh or reset worktree: paths this provider integrated earlier (`ours`), overlaid
|
|
815
|
+
* read inputs, dependency links; then records the baseline the change set is computed against.
|
|
816
|
+
*/
|
|
817
|
+
const populate = async (lease, input, signal) => {
|
|
818
|
+
const target = lease.target;
|
|
819
|
+
const owned = input.packet.scope.owned_paths;
|
|
820
|
+
const seeded = new Map();
|
|
821
|
+
const copyFromMain = async (relative) => {
|
|
822
|
+
const observed = await observe(deps.workspaceRoot, relative);
|
|
823
|
+
if (observed.kind === "file")
|
|
824
|
+
await writeSafe(target, relative, observed.bytes, platform);
|
|
825
|
+
else if (observed.kind === "absent")
|
|
826
|
+
await removeSafe(target, relative, platform);
|
|
827
|
+
else
|
|
828
|
+
return false;
|
|
829
|
+
seeded.set(relative, observed.kind === "file" ? observed.bytes : null);
|
|
830
|
+
return true;
|
|
831
|
+
};
|
|
832
|
+
for (const relative of input.ours)
|
|
833
|
+
await copyFromMain(relative);
|
|
834
|
+
lease.seededMainIdentities = input.ours.length === 0 ? new Map() : await contentIdentities(deps.workspaceRoot, input.ours, signal, { git, platform });
|
|
835
|
+
const overlaid = [];
|
|
836
|
+
if (input.overlay.length > 0) {
|
|
837
|
+
for (const relative of input.mainDirty) {
|
|
838
|
+
if (seeded.has(relative) || matchesAny(relative, owned, platform) || !matchesAny(relative, input.overlay, platform))
|
|
839
|
+
continue;
|
|
840
|
+
if (await copyFromMain(relative))
|
|
841
|
+
overlaid.push(normalizeWorkspacePath(relative) ?? relative);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
// Dependency links: ignored, present in the main tree, not overlapping any owned path.
|
|
845
|
+
const links = [];
|
|
846
|
+
for (const entry of await gitIgnoredEntries(git, deps.workspaceRoot, signal)) {
|
|
847
|
+
if (!entry.endsWith("/"))
|
|
848
|
+
continue;
|
|
849
|
+
const relative = entry.replace(/\/+$/, "");
|
|
850
|
+
const name = relative.split("/").at(-1) ?? "";
|
|
851
|
+
if (!DEPENDENCY_NAMES.has(name) || owned.some((pattern) => pathPatternsOverlap(pattern, relative)))
|
|
852
|
+
continue;
|
|
853
|
+
const parent = path.dirname(path.join(target, ...relative.split("/")));
|
|
854
|
+
const parentInfo = await lstat(parent).catch(() => undefined);
|
|
855
|
+
if (parentInfo === undefined || !parentInfo.isDirectory() || parentInfo.isSymbolicLink())
|
|
856
|
+
continue;
|
|
857
|
+
if ((await lstat(path.join(target, ...relative.split("/"))).catch(() => undefined)) !== undefined)
|
|
858
|
+
continue;
|
|
859
|
+
links.push(relative);
|
|
860
|
+
}
|
|
861
|
+
if (links.length > 0) {
|
|
862
|
+
// Record the links before creating them: a crash in between must still unlink them before removal.
|
|
863
|
+
lease.links = links;
|
|
864
|
+
await writeJsonAtomic(ownerFile(lease.ownerAttemptId), ownerOf(lease.ownerAttemptId, "worktree", links));
|
|
865
|
+
for (const relative of links) {
|
|
866
|
+
await symlink(path.join(deps.workspaceRoot, ...relative.split("/")), path.join(target, ...relative.split("/")), platform === "win32" ? "junction" : "dir");
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
const ownedGitlinks = input.submodules.filter((gitlink) => owned.some((pattern) => pathPatternsOverlap(pattern, gitlink)));
|
|
870
|
+
const literalOwned = owned.filter((pattern) => isLiteralPattern(pattern));
|
|
871
|
+
const excludedPrefixes = [...links];
|
|
872
|
+
const content = async (relative) => {
|
|
873
|
+
if (seeded.has(relative))
|
|
874
|
+
return seeded.get(relative) ?? null;
|
|
875
|
+
return (await gitShowFiltered(git, target, input.head, relative)) ?? null;
|
|
876
|
+
};
|
|
877
|
+
let flagged = new Map();
|
|
878
|
+
const baseline = {
|
|
879
|
+
beforeContent: content,
|
|
880
|
+
beforeDigest: async (relative) => {
|
|
881
|
+
const bytes = await content(relative);
|
|
882
|
+
return bytes === null ? null : workspaceDigest(bytes);
|
|
883
|
+
},
|
|
884
|
+
async candidates(inner) {
|
|
885
|
+
const found = new Set([...(await gitDirtyPaths(git, target, inner)), ...seeded.keys()]);
|
|
886
|
+
// Owned paths are also scanned directly (B8): an owned literal file git ignores, and anything
|
|
887
|
+
// written inside a submodule directory, which the superproject's status never reports.
|
|
888
|
+
const literalFiles = [];
|
|
889
|
+
for (const pattern of literalOwned) {
|
|
890
|
+
if ((await observe(target, pattern)).kind === "file" && !found.has(pattern))
|
|
891
|
+
literalFiles.push(pattern);
|
|
892
|
+
}
|
|
893
|
+
for (const relative of await gitIgnoredSubset(git, target, literalFiles, inner))
|
|
894
|
+
found.add(relative);
|
|
895
|
+
const nextFlagged = new Map();
|
|
896
|
+
for (const gitlink of ownedGitlinks) {
|
|
897
|
+
for (const entry of await walkTree(path.join(target, ...gitlink.split("/")), 50_000, { skipDirectory: (_relative, name) => name === ".git" })) {
|
|
898
|
+
const relative = `${gitlink}/${entry.relative}`;
|
|
899
|
+
found.add(relative);
|
|
900
|
+
nextFlagged.set(relative, `it is inside the submodule ${gitlink}; Synorch never integrates writes inside a submodule`);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
flagged = nextFlagged;
|
|
904
|
+
const kept = [];
|
|
905
|
+
for (const relative of found) {
|
|
906
|
+
if (excludedPrefixes.some((prefix) => isAtOrBelow(relative, prefix, platform)))
|
|
907
|
+
continue;
|
|
908
|
+
// A new file inside an unignored generated directory (build output, caches) is not part of the artifact.
|
|
909
|
+
if (inGeneratedDirectory(relative, owned, platform) && !seeded.has(relative) && (await content(relative)) === null)
|
|
910
|
+
continue;
|
|
911
|
+
kept.push(relative);
|
|
912
|
+
}
|
|
913
|
+
return kept;
|
|
914
|
+
},
|
|
915
|
+
flagged: () => flagged,
|
|
916
|
+
};
|
|
917
|
+
return { baseline, overlaid };
|
|
918
|
+
};
|
|
919
|
+
/**
|
|
920
|
+
* Ignored links in the main workspace that the artifact's `.gitignore` changes would expose.
|
|
921
|
+
* `worktreeRoot` already holds the new ignore files, so `check-ignore --no-index` there answers
|
|
922
|
+
* "is this still ignored afterwards".
|
|
923
|
+
*/
|
|
924
|
+
const exposedIgnoredLinks = async (changes, worktreeRoot, signal) => {
|
|
925
|
+
const ignoreFiles = changes.filter((change) => change.path === ".gitignore" || change.path.endsWith("/.gitignore"));
|
|
926
|
+
if (ignoreFiles.length === 0)
|
|
927
|
+
return [];
|
|
928
|
+
const scopes = ignoreFiles.map((change) => (change.path === ".gitignore" ? "" : change.path.slice(0, -"/.gitignore".length)));
|
|
929
|
+
const ignored = (await gitIgnoredEntries(git, deps.workspaceRoot, signal)).filter((entry) => scopes.some((scope) => scope === "" || entry === scope || entry.startsWith(`${scope}/`)));
|
|
930
|
+
const stillIgnored = await gitCheckIgnored(git, worktreeRoot, ignored, signal);
|
|
931
|
+
const exposed = [];
|
|
932
|
+
for (const entry of ignored.filter((candidate) => !stillIgnored.has(candidate))) {
|
|
933
|
+
const relative = entry.replace(/\/+$/, "");
|
|
934
|
+
const links = (await observe(deps.workspaceRoot, relative)).kind === "link" ? [relative] : [];
|
|
935
|
+
if (links.length === 0) {
|
|
936
|
+
for (const child of await walkTree(path.join(deps.workspaceRoot, ...relative.split("/")), 10_000).catch(() => [{ relative: "", kind: "link" }])) {
|
|
937
|
+
if (child.kind === "link")
|
|
938
|
+
links.push(child.relative === "" ? relative : `${relative}/${child.relative}`);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
exposed.push(...links);
|
|
942
|
+
}
|
|
943
|
+
return exposed;
|
|
944
|
+
};
|
|
945
|
+
/** A worktree workspace over `lease`, with the lease's dispose semantics (only the current holder removes it). */
|
|
946
|
+
const worktreeWorkspace = (attemptId, packet, head, lease, baseline, extras) => {
|
|
947
|
+
const workspace = build(attemptId, packet, "worktree", lease.target, head, baseline, async (self) => {
|
|
948
|
+
leases.delete(self);
|
|
949
|
+
// A workspace superseded by a reuse no longer owns the worktree; the reusing one disposes it.
|
|
950
|
+
if (lease.holder !== self || lease.closed)
|
|
951
|
+
return;
|
|
952
|
+
await discardLease(lease);
|
|
953
|
+
}, { ...extras, dependencyLinks: [...lease.links] });
|
|
954
|
+
lease.holder = workspace;
|
|
955
|
+
leases.set(workspace, lease);
|
|
956
|
+
return workspace;
|
|
957
|
+
};
|
|
958
|
+
const openWorktree = async (packet, attemptId, input, extras, signal) => {
|
|
959
|
+
const lease = { target: worktreePath(attemptId), ownerAttemptId: attemptId, links: [], holder: undefined, closed: false, seededMainIdentities: new Map() };
|
|
960
|
+
try {
|
|
961
|
+
await mkdir(path.dirname(lease.target), { recursive: true });
|
|
962
|
+
await writeJsonAtomic(ownerFile(attemptId), ownerOf(attemptId, "worktree"));
|
|
963
|
+
await git(["worktree", "add", "--detach", lease.target, input.head], deps.workspaceRoot, signal);
|
|
964
|
+
const { baseline, overlaid } = await populate(lease, input, signal);
|
|
965
|
+
return worktreeWorkspace(attemptId, packet, input.head, lease, baseline, { ...extras, ...(overlaid.length > 0 ? { overlaid } : {}) });
|
|
966
|
+
}
|
|
967
|
+
catch (error) {
|
|
968
|
+
await discardLease(lease);
|
|
969
|
+
throw error;
|
|
970
|
+
}
|
|
971
|
+
};
|
|
972
|
+
const reuseWorktree = async (previous, packet, attemptId, input, extras, signal) => {
|
|
973
|
+
const lease = leases.get(previous);
|
|
974
|
+
if (lease === undefined || lease.closed || lease.holder !== previous)
|
|
975
|
+
return undefined;
|
|
976
|
+
try {
|
|
977
|
+
for (const link of lease.links)
|
|
978
|
+
await unlinkLink(path.join(lease.target, ...link.split("/")));
|
|
979
|
+
lease.links = [];
|
|
980
|
+
await writeJsonAtomic(ownerFile(lease.ownerAttemptId), ownerOf(lease.ownerAttemptId, "worktree"));
|
|
981
|
+
await git(["checkout", "-q", "-f", "--detach", input.head], lease.target, signal);
|
|
982
|
+
await git(["reset", "-q", "--hard", input.head], lease.target, signal);
|
|
983
|
+
await cleanUntracked(lease.target, signal);
|
|
984
|
+
leases.delete(previous);
|
|
985
|
+
const { baseline, overlaid } = await populate(lease, input, signal);
|
|
986
|
+
return worktreeWorkspace(attemptId, packet, input.head, lease, baseline, { ...extras, reused: true, ...(overlaid.length > 0 ? { overlaid } : {}) });
|
|
987
|
+
}
|
|
988
|
+
catch (error) {
|
|
989
|
+
if (signal.aborted)
|
|
990
|
+
throw error;
|
|
991
|
+
leases.delete(previous);
|
|
992
|
+
await discardLease(lease);
|
|
993
|
+
return undefined;
|
|
994
|
+
}
|
|
995
|
+
};
|
|
996
|
+
const refuseOwnedInsideSubmodule = (owned, submodules) => {
|
|
997
|
+
for (const pattern of owned) {
|
|
998
|
+
const prefix = staticPrefix(pattern);
|
|
999
|
+
const gitlink = submodules.find((candidate) => segmentsUnder(candidate.split("/"), prefix, platform));
|
|
1000
|
+
if (gitlink !== undefined) {
|
|
1001
|
+
throw isolationError("policy_denied", `owned path ${pattern} is inside the submodule ${gitlink}; Synorch does not write inside submodules (own paths outside it, or work in the submodule's own repository)`);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
};
|
|
1005
|
+
return {
|
|
1006
|
+
worktreePath,
|
|
1007
|
+
async create(packet, attemptId, signal, options) {
|
|
1008
|
+
if (packet.write_mode !== "owned-paths" || packet.scope.owned_paths.length === 0) {
|
|
1009
|
+
const root = options?.readRoot ?? deps.workspaceRoot;
|
|
1010
|
+
return build(attemptId, packet, "shared-read-only", root, undefined, undefined, async () => { });
|
|
1011
|
+
}
|
|
1012
|
+
const owned = packet.scope.owned_paths;
|
|
1013
|
+
const top = await gitTopLevel(git, deps.workspaceRoot);
|
|
1014
|
+
const isGitRoot = top !== undefined && (await samePath(top, deps.workspaceRoot, platform));
|
|
1015
|
+
const head = isGitRoot ? await gitHead(git, deps.workspaceRoot) : undefined;
|
|
1016
|
+
let reason = isGitRoot ? (head === undefined ? "the repository has no commit" : undefined) : "the workspace is not a git repository";
|
|
1017
|
+
const submodules = isGitRoot ? await gitSubmodulePaths(git, deps.workspaceRoot, signal) : [];
|
|
1018
|
+
refuseOwnedInsideSubmodule(owned, submodules);
|
|
1019
|
+
const ours = [];
|
|
1020
|
+
let mainDirty = [];
|
|
1021
|
+
if (reason === undefined) {
|
|
1022
|
+
mainDirty = await gitDirtyPaths(git, deps.workspaceRoot, signal);
|
|
1023
|
+
for (const relative of mainDirty) {
|
|
1024
|
+
const current = observedDigest(await observe(deps.workspaceRoot, relative));
|
|
1025
|
+
const key = normalizeWorkspacePath(relative) ?? relative;
|
|
1026
|
+
if (integrated.has(key) && integrated.get(key) === current)
|
|
1027
|
+
ours.push(relative);
|
|
1028
|
+
}
|
|
1029
|
+
const overlap = mainDirty.filter((relative) => !ours.includes(relative) && matchesAny(relative, owned, platform));
|
|
1030
|
+
if (overlap.length > 0)
|
|
1031
|
+
reason = `uncommitted changes overlap owned paths (${overlap.slice(0, 5).join(", ")})`;
|
|
1032
|
+
}
|
|
1033
|
+
const common = submodules.length > 0 ? { submodules } : {};
|
|
1034
|
+
let fallback;
|
|
1035
|
+
if (packet.isolation === "worktree" && reason === undefined && head !== undefined) {
|
|
1036
|
+
const input = { packet, head, ours, mainDirty, overlay: options?.overlay ?? [], submodules };
|
|
1037
|
+
const previous = options?.reuse;
|
|
1038
|
+
if (previous !== undefined) {
|
|
1039
|
+
const reused = await reuseWorktree(previous, packet, attemptId, input, common, signal);
|
|
1040
|
+
if (reused !== undefined)
|
|
1041
|
+
return reused;
|
|
1042
|
+
}
|
|
1043
|
+
try {
|
|
1044
|
+
return await openWorktree(packet, attemptId, input, common, signal);
|
|
1045
|
+
}
|
|
1046
|
+
catch (error) {
|
|
1047
|
+
if (signal.aborted)
|
|
1048
|
+
throw error;
|
|
1049
|
+
const failure = classifyWorktreeFailure(error);
|
|
1050
|
+
if (packet.risk === "high-risk") {
|
|
1051
|
+
throw isolationError("sandbox_insufficient", `high-risk writing task requires a worktree, but creating it failed (${failure.reason}): ${failure.detail}`);
|
|
1052
|
+
}
|
|
1053
|
+
fallback = { from: "worktree", reason: failure.reason, detail: failure.detail };
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
else if (packet.isolation === "worktree" && top === undefined && !(await gitAvailable(git, deps.workspaceRoot))) {
|
|
1057
|
+
fallback = { from: "worktree", reason: "git-unavailable", detail: "git could not be started" };
|
|
1058
|
+
reason = "git is not available";
|
|
1059
|
+
}
|
|
1060
|
+
if (packet.risk === "high-risk") {
|
|
1061
|
+
throw isolationError("sandbox_insufficient", `high-risk writing task requires a worktree, but ${reason ?? "scoped-dir was requested"}`);
|
|
1062
|
+
}
|
|
1063
|
+
const slot = await acquireSlot(packet.task_id, signal);
|
|
1064
|
+
try {
|
|
1065
|
+
const skipDirs = isGitRoot
|
|
1066
|
+
? (await gitIgnoredEntries(git, deps.workspaceRoot, signal))
|
|
1067
|
+
.filter((entry) => entry.endsWith("/"))
|
|
1068
|
+
.map((entry) => entry.replace(/\/+$/, ""))
|
|
1069
|
+
.filter((relative) => relative.length > 0 && !ownsInside(owned, relative, platform))
|
|
1070
|
+
: [];
|
|
1071
|
+
// Clean tracked files outside the owned paths are restored from the base commit (checkout
|
|
1072
|
+
// form, digest-verified) instead of copied; owned files always keep a copy.
|
|
1073
|
+
let restorableFromGit = new Set();
|
|
1074
|
+
if (isGitRoot && head !== undefined) {
|
|
1075
|
+
const dirty = new Set(reason === undefined ? mainDirty : await gitDirtyPaths(git, deps.workspaceRoot, signal));
|
|
1076
|
+
restorableFromGit = new Set((await gitTrackedPaths(git, deps.workspaceRoot, signal)).filter((relative) => !dirty.has(relative) && !matchesAny(relative, owned, platform)));
|
|
1077
|
+
}
|
|
1078
|
+
const snapshot = await ScopedSnapshot.take({
|
|
1079
|
+
root: deps.workspaceRoot,
|
|
1080
|
+
directory: scopedDirectory(attemptId),
|
|
1081
|
+
owner: ownerOf(attemptId, "scoped-dir"),
|
|
1082
|
+
ownedPaths: owned,
|
|
1083
|
+
git: isGitRoot && head !== undefined ? { runner: git, head } : undefined,
|
|
1084
|
+
skipDirs,
|
|
1085
|
+
platform,
|
|
1086
|
+
restorableFromGit,
|
|
1087
|
+
});
|
|
1088
|
+
scopedSnapshots.set(attemptId, snapshot);
|
|
1089
|
+
return build(attemptId, packet, "scoped-dir", deps.workspaceRoot, head, snapshot, async () => {
|
|
1090
|
+
scopedSnapshots.delete(attemptId);
|
|
1091
|
+
await snapshot.destroy();
|
|
1092
|
+
}, { ...common, ...(fallback === undefined ? {} : { fallback }) }, slot);
|
|
1093
|
+
}
|
|
1094
|
+
catch (error) {
|
|
1095
|
+
slot.release();
|
|
1096
|
+
await rm(scopedDirectory(attemptId), { recursive: true, force: true }).catch(() => undefined);
|
|
1097
|
+
throw error;
|
|
1098
|
+
}
|
|
1099
|
+
},
|
|
1100
|
+
async integrate(workspace, expectedArtifact, signal) {
|
|
1101
|
+
const orchestrated = workspace;
|
|
1102
|
+
if (typeof orchestrated.changeSet !== "function" || orchestrated.ownedPaths === undefined || orchestrated.forbiddenPaths === undefined) {
|
|
1103
|
+
throw isolationError("internal", "integrate needs a workspace created by this provider");
|
|
1104
|
+
}
|
|
1105
|
+
const set = await orchestrated.changeSet(signal);
|
|
1106
|
+
if (set.artifactDigest !== expectedArtifact) {
|
|
1107
|
+
throw isolationError("verification_failed", `artifact changed since it was pinned: expected ${expectedArtifact}, found ${set.artifactDigest}`);
|
|
1108
|
+
}
|
|
1109
|
+
const violations = findScopeViolations([...set.changes.map((change) => change.path), ...set.unsafe.filter((entry) => normalizeWorkspacePath(entry) === undefined)], { owned: orchestrated.ownedPaths, forbidden: orchestrated.forbiddenPaths }, platform);
|
|
1110
|
+
if (violations.length > 0) {
|
|
1111
|
+
throw isolationError("policy_denied", `artifact touches paths outside the owned scope: ${violations.map((v) => `${v.path} (${v.reason})`).join(", ")}`);
|
|
1112
|
+
}
|
|
1113
|
+
if (set.unsafe.length > 0) {
|
|
1114
|
+
const reasons = set.unsafe.filter((entry) => set.unsafeReasons?.has(entry));
|
|
1115
|
+
if (reasons.length > 0) {
|
|
1116
|
+
throw isolationError("policy_denied", `artifact contains paths that cannot be integrated: ${reasons.slice(0, 10).map((entry) => `${entry}: ${set.unsafeReasons?.get(entry) ?? ""}`).join("; ")}`);
|
|
1117
|
+
}
|
|
1118
|
+
throw isolationError("policy_denied", `artifact contains links or special files, which are never integrated: ${set.unsafe.slice(0, 10).join(", ")}`);
|
|
1119
|
+
}
|
|
1120
|
+
if (workspace.mode === "shared-read-only") {
|
|
1121
|
+
if (set.changes.length > 0)
|
|
1122
|
+
throw isolationError("policy_denied", "a read-only workspace has changes");
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1125
|
+
if (workspace.mode === "scoped-dir") {
|
|
1126
|
+
for (const change of set.changes)
|
|
1127
|
+
integrated.set(change.path, change.after);
|
|
1128
|
+
await scopedSnapshots.get(orchestrated.attemptId)?.markIntegrated();
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1131
|
+
const lease = leases.get(workspace);
|
|
1132
|
+
const canonicalRoot = await realpath(deps.workspaceRoot);
|
|
1133
|
+
const refused = [];
|
|
1134
|
+
for (const link of await exposedIgnoredLinks(set.changes, workspace.root, signal)) {
|
|
1135
|
+
const real = await realpath(path.join(deps.workspaceRoot, ...link.split("/"))).catch(() => undefined);
|
|
1136
|
+
const inside = real !== undefined && contains(canonicalRoot, real);
|
|
1137
|
+
if (!inside || !matchesAny(link, orchestrated.ownedPaths, platform))
|
|
1138
|
+
refused.push(link);
|
|
1139
|
+
}
|
|
1140
|
+
if (refused.length > 0) {
|
|
1141
|
+
throw isolationError("policy_denied", `a .gitignore change would expose ignored links (${refused.slice(0, 5).join(", ")}); only an owned link inside the workspace may be un-ignored`);
|
|
1142
|
+
}
|
|
1143
|
+
const paths = set.changes.map((change) => change.path);
|
|
1144
|
+
const worktreeName = (relative) => set.diskPaths?.get(relative) ?? relative;
|
|
1145
|
+
// Where each path lives in the main tree (an existing NFD-named file keeps its spelling).
|
|
1146
|
+
const mainName = new Map();
|
|
1147
|
+
for (const relative of paths)
|
|
1148
|
+
mainName.set(relative, (await resolveOnDiskPath(deps.workspaceRoot, relative, platform)) ?? relative);
|
|
1149
|
+
// Resolve every target before writing any byte: a link anywhere refuses the whole artifact.
|
|
1150
|
+
for (const relative of paths)
|
|
1151
|
+
await resolveLinkSafeTarget(deps.workspaceRoot, mainName.get(relative) ?? relative, platform);
|
|
1152
|
+
// Base identities: the base commit's blob, or (for paths this provider integrated earlier) the main tree at create time.
|
|
1153
|
+
const seeded = lease?.seededMainIdentities ?? new Map();
|
|
1154
|
+
const base = new Map();
|
|
1155
|
+
const gitlinkBases = [];
|
|
1156
|
+
const fromTree = paths.filter((relative) => !seeded.has(worktreeName(relative)));
|
|
1157
|
+
const tree = workspace.baseCommit === undefined ? new Map() : await gitTreeEntries(git, workspace.root, workspace.baseCommit, fromTree.map(worktreeName), signal);
|
|
1158
|
+
for (const relative of paths) {
|
|
1159
|
+
if (seeded.has(worktreeName(relative))) {
|
|
1160
|
+
base.set(relative, seeded.get(worktreeName(relative)) ?? null);
|
|
1161
|
+
continue;
|
|
1162
|
+
}
|
|
1163
|
+
const entry = tree.get(worktreeName(relative));
|
|
1164
|
+
if (entry?.mode === "160000")
|
|
1165
|
+
gitlinkBases.push(relative);
|
|
1166
|
+
base.set(relative, entry === undefined ? null : { scheme: "git-blob", oid: entry.oid });
|
|
1167
|
+
}
|
|
1168
|
+
if (gitlinkBases.length > 0) {
|
|
1169
|
+
throw isolationError("policy_denied", `artifact replaces submodule entries, which are never integrated: ${gitlinkBases.slice(0, 5).join(", ")}`);
|
|
1170
|
+
}
|
|
1171
|
+
// Written content in the main tree's representation: clean in the worktree (blob), smudge for the main tree.
|
|
1172
|
+
const writes = set.changes.filter((change) => set.contents.get(change.path) !== null && set.contents.get(change.path) !== undefined);
|
|
1173
|
+
const writeNames = writes.map((change) => worktreeName(change.path));
|
|
1174
|
+
const ignoredInWorktree = await gitIgnoredSubset(git, workspace.root, writeNames, signal);
|
|
1175
|
+
const blobs = await gitHashObjects(git, workspace.root, writeNames.filter((name) => !ignoredInWorktree.has(name)), { write: true, signal });
|
|
1176
|
+
const ignoredInMain = await gitIgnoredSubset(git, deps.workspaceRoot, writes.map((change) => mainName.get(change.path) ?? change.path), signal);
|
|
1177
|
+
const finalBytes = new Map();
|
|
1178
|
+
const after = new Map();
|
|
1179
|
+
for (const change of set.changes) {
|
|
1180
|
+
const raw = set.contents.get(change.path);
|
|
1181
|
+
if (raw === null || raw === undefined) {
|
|
1182
|
+
after.set(change.path, null);
|
|
1183
|
+
continue;
|
|
1184
|
+
}
|
|
1185
|
+
const target = mainName.get(change.path) ?? change.path;
|
|
1186
|
+
const oid = blobs.get(worktreeName(change.path));
|
|
1187
|
+
if (oid === undefined || ignoredInMain.has(target)) {
|
|
1188
|
+
finalBytes.set(change.path, raw);
|
|
1189
|
+
after.set(change.path, { scheme: "workspace", digest: workspaceDigest(raw) });
|
|
1190
|
+
continue;
|
|
1191
|
+
}
|
|
1192
|
+
let bytes = await gitSmudgeBlob(git, deps.workspaceRoot, oid, target, signal);
|
|
1193
|
+
const existing = await observe(deps.workspaceRoot, target);
|
|
1194
|
+
if (existing.kind === "file" && !existing.bytes.includes(0) && !bytes.includes(0)) {
|
|
1195
|
+
const mainStyle = eolStyle(existing.bytes);
|
|
1196
|
+
const newStyle = eolStyle(bytes);
|
|
1197
|
+
if ((mainStyle === "lf" || mainStyle === "crlf") && (newStyle === "lf" || newStyle === "crlf") && mainStyle !== newStyle) {
|
|
1198
|
+
// Keep the main file's line endings when git stores the very same blob either way.
|
|
1199
|
+
const candidate = withEol(bytes, mainStyle);
|
|
1200
|
+
if ((await gitHashBytes(git, deps.workspaceRoot, target, candidate, signal)) === oid)
|
|
1201
|
+
bytes = candidate;
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
finalBytes.set(change.path, bytes);
|
|
1205
|
+
after.set(change.path, { scheme: "git-blob", oid });
|
|
1206
|
+
}
|
|
1207
|
+
const current = await contentIdentities(deps.workspaceRoot, paths, signal, { git, platform });
|
|
1208
|
+
const conflicts = [];
|
|
1209
|
+
for (const relative of paths) {
|
|
1210
|
+
const now = current.get(relative);
|
|
1211
|
+
const was = base.get(relative) ?? null;
|
|
1212
|
+
const next = after.get(relative) ?? null;
|
|
1213
|
+
const unchanged = now === undefined ? was === null : was !== null && sameContent(now, was);
|
|
1214
|
+
const alreadyApplied = now === undefined ? next === null : next !== null && sameContent(now, next);
|
|
1215
|
+
if (!unchanged && !alreadyApplied)
|
|
1216
|
+
conflicts.push(relative);
|
|
1217
|
+
}
|
|
1218
|
+
if (conflicts.length > 0) {
|
|
1219
|
+
throw isolationError("verification_failed", `integration conflict: the main workspace changed at ${conflicts.join(", ")}`);
|
|
1220
|
+
}
|
|
1221
|
+
for (const change of set.changes) {
|
|
1222
|
+
const target = mainName.get(change.path) ?? change.path;
|
|
1223
|
+
const bytes = finalBytes.get(change.path);
|
|
1224
|
+
if (bytes === undefined)
|
|
1225
|
+
await removeSafe(deps.workspaceRoot, target, platform);
|
|
1226
|
+
else
|
|
1227
|
+
await writeSafe(deps.workspaceRoot, target, bytes, platform);
|
|
1228
|
+
const digest = bytes === undefined ? null : workspaceDigest(bytes);
|
|
1229
|
+
integrated.set(change.path, digest);
|
|
1230
|
+
for (const slot of slots)
|
|
1231
|
+
slot.foreign.set(change.path, digest);
|
|
1232
|
+
}
|
|
1233
|
+
},
|
|
1234
|
+
async seed(workspace, artifactBytes) {
|
|
1235
|
+
if (workspace.mode === "shared-read-only")
|
|
1236
|
+
throw isolationError("policy_denied", "cannot seed a read-only workspace");
|
|
1237
|
+
const document = decodeArtifact(artifactBytes);
|
|
1238
|
+
for (const change of document.changes)
|
|
1239
|
+
await resolveLinkSafeTarget(workspace.root, change.path, platform);
|
|
1240
|
+
for (const change of document.changes) {
|
|
1241
|
+
if (change.content === null)
|
|
1242
|
+
await removeSafe(workspace.root, change.path, platform);
|
|
1243
|
+
else
|
|
1244
|
+
await writeSafe(workspace.root, change.path, Buffer.from(change.content, "base64"), platform);
|
|
1245
|
+
}
|
|
1246
|
+
},
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
function processAlive(pid) {
|
|
1250
|
+
try {
|
|
1251
|
+
process.kill(pid, 0);
|
|
1252
|
+
return true;
|
|
1253
|
+
}
|
|
1254
|
+
catch (error) {
|
|
1255
|
+
return error.code !== "ESRCH";
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
/** The default liveness test: same host, and the owner process still runs (this process counts as live). */
|
|
1259
|
+
export function isAttemptOwnerLive(owner) {
|
|
1260
|
+
if (owner.host !== hostname())
|
|
1261
|
+
return true;
|
|
1262
|
+
return owner.pid === process.pid || processAlive(owner.pid);
|
|
1263
|
+
}
|
|
1264
|
+
function parseOwner(value) {
|
|
1265
|
+
const owner = value;
|
|
1266
|
+
if (owner?.format !== ATTEMPT_OWNER_FORMAT || typeof owner.pid !== "number" || typeof owner.host !== "string" || typeof owner.attempt_id !== "string")
|
|
1267
|
+
return undefined;
|
|
1268
|
+
return owner;
|
|
1269
|
+
}
|
|
1270
|
+
/**
|
|
1271
|
+
* Removes attempt workspaces a crashed process left under the project's worktrees directory (the
|
|
1272
|
+
* hashed one, and the pre-ADR-19 `<worktrees>/<project-id>`): git worktrees are removed with their
|
|
1273
|
+
* dependency links unlinked first, and a scoped-dir attempt's partial writes inside its owned
|
|
1274
|
+
* paths are reverted from the persisted snapshot (changes elsewhere are reported, not reverted,
|
|
1275
|
+
* because the user may have edited those files since). Nothing whose owner is live is touched.
|
|
1276
|
+
* Integrated scoped-dir attempts are only cleaned up.
|
|
1277
|
+
*/
|
|
1278
|
+
export async function pruneOrphanedAttempts(options) {
|
|
1279
|
+
const git = options.git ?? runGit;
|
|
1280
|
+
const platform = options.platform ?? process.platform;
|
|
1281
|
+
const isLive = options.isLive ?? isAttemptOwnerLive;
|
|
1282
|
+
const report = { removedWorktrees: [], revertedScoped: [], live: [], unknown: [] };
|
|
1283
|
+
const directories = [...new Set([projectWorkspacesDirectory(options.worktreesRoot, options.projectId), path.join(options.worktreesRoot, options.projectId)])];
|
|
1284
|
+
for (const directory of directories) {
|
|
1285
|
+
let names;
|
|
1286
|
+
try {
|
|
1287
|
+
names = await readdir(directory);
|
|
1288
|
+
}
|
|
1289
|
+
catch {
|
|
1290
|
+
continue;
|
|
1291
|
+
}
|
|
1292
|
+
const owned = new Set();
|
|
1293
|
+
for (const name of names.filter((entry) => entry.endsWith(".owner.json"))) {
|
|
1294
|
+
const base = name.slice(0, -".owner.json".length);
|
|
1295
|
+
owned.add(base);
|
|
1296
|
+
const owner = parseOwner(await readJson(path.join(directory, name)));
|
|
1297
|
+
if (owner === undefined) {
|
|
1298
|
+
report.unknown.push(base);
|
|
1299
|
+
continue;
|
|
1300
|
+
}
|
|
1301
|
+
if (isLive(owner)) {
|
|
1302
|
+
report.live.push(owner.attempt_id);
|
|
1303
|
+
continue;
|
|
1304
|
+
}
|
|
1305
|
+
const target = path.join(directory, base);
|
|
1306
|
+
const links = [...(owner.dependency_links ?? []), ...DEPENDENCY_LINK_DIRECTORIES];
|
|
1307
|
+
for (const link of links) {
|
|
1308
|
+
const normalized = normalizeWorkspacePath(link);
|
|
1309
|
+
if (normalized !== undefined)
|
|
1310
|
+
await unlinkLink(path.join(target, ...normalized.split("/")));
|
|
1311
|
+
}
|
|
1312
|
+
// Node's rm never descends into a link; `git worktree remove` would empty a junction's target.
|
|
1313
|
+
await rm(target, { recursive: true, force: true, maxRetries: 3 }).catch(() => undefined);
|
|
1314
|
+
await rm(path.join(directory, name), { force: true }).catch(() => undefined);
|
|
1315
|
+
report.removedWorktrees.push(owner.attempt_id);
|
|
1316
|
+
}
|
|
1317
|
+
for (const name of names.filter((entry) => entry.endsWith(".scoped"))) {
|
|
1318
|
+
const base = name.slice(0, -".scoped".length);
|
|
1319
|
+
const scoped = path.join(directory, name);
|
|
1320
|
+
const owner = parseOwner(await readJson(path.join(scoped, SCOPED_OWNER_FILE)));
|
|
1321
|
+
if (owner === undefined) {
|
|
1322
|
+
report.unknown.push(base);
|
|
1323
|
+
continue;
|
|
1324
|
+
}
|
|
1325
|
+
if (isLive(owner)) {
|
|
1326
|
+
report.live.push(owner.attempt_id);
|
|
1327
|
+
continue;
|
|
1328
|
+
}
|
|
1329
|
+
const state = (await readJson(path.join(scoped, SCOPED_STATE_FILE)))?.state;
|
|
1330
|
+
const snapshot = await ScopedSnapshot.load(scoped, git, platform);
|
|
1331
|
+
if (snapshot !== undefined && state !== "integrated") {
|
|
1332
|
+
if (!(await samePath(snapshot.workspaceRoot, options.workspaceRoot, platform))) {
|
|
1333
|
+
report.unknown.push(owner.attempt_id);
|
|
1334
|
+
continue;
|
|
1335
|
+
}
|
|
1336
|
+
const changed = [...(await snapshot.candidates())].sort();
|
|
1337
|
+
const inside = changed.filter((relative) => matchesAny(relative, snapshot.ownedPaths, platform));
|
|
1338
|
+
const outcome = await restorePaths(options.workspaceRoot, snapshot, inside, platform);
|
|
1339
|
+
report.revertedScoped.push({
|
|
1340
|
+
attemptId: owner.attempt_id,
|
|
1341
|
+
restored: outcome.restored,
|
|
1342
|
+
leftInPlace: changed.filter((relative) => !inside.includes(relative)),
|
|
1343
|
+
unrestorable: outcome.unrestorable,
|
|
1344
|
+
});
|
|
1345
|
+
}
|
|
1346
|
+
await rm(scoped, { recursive: true, force: true }).catch(() => undefined);
|
|
1347
|
+
}
|
|
1348
|
+
for (const name of names) {
|
|
1349
|
+
if (name.endsWith(".owner.json") || name.endsWith(".scoped") || owned.has(name))
|
|
1350
|
+
continue;
|
|
1351
|
+
report.unknown.push(name);
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
await git(["worktree", "prune"], options.workspaceRoot).catch(() => undefined);
|
|
1355
|
+
return report;
|
|
1356
|
+
}
|
|
1357
|
+
//# sourceMappingURL=isolation.js.map
|