@sema-agent/core 7.19.0 → 7.20.1

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.
Files changed (114) hide show
  1. package/CHANGELOG.md +199 -2
  2. package/dist/agents/subagent.js +12 -20
  3. package/dist/agents/teacher.js +2 -5
  4. package/dist/agents/verify.js +21 -10
  5. package/dist/brain/anthropic.js +3 -1
  6. package/dist/brain/open-responses.js +4 -2
  7. package/dist/brain/openai.js +3 -1
  8. package/dist/brain/stream-engine.d.ts +4 -0
  9. package/dist/brain/stream-engine.js +4 -0
  10. package/dist/core/auto-mode.d.ts +37 -18
  11. package/dist/core/auto-mode.js +1 -1
  12. package/dist/core/child-clamp-seats.d.ts +79 -0
  13. package/dist/core/child-clamp-seats.js +29 -0
  14. package/dist/core/engine-notice.d.ts +76 -49
  15. package/dist/core/fs-write-gate-policy.d.ts +17 -9
  16. package/dist/core/fs-write-gate-policy.js +51 -4
  17. package/dist/core/gate-bounded-await.d.ts +38 -0
  18. package/dist/core/gate-bounded-await.js +53 -0
  19. package/dist/core/gate-fold.js +16 -48
  20. package/dist/core/gate-lanes.js +11 -3
  21. package/dist/core/gate-outcome.d.ts +9 -6
  22. package/dist/core/governance-codes.d.ts +16 -1
  23. package/dist/core/governance-codes.js +14 -0
  24. package/dist/core/hooks.d.ts +8 -8
  25. package/dist/core/memory-engine/engine.d.ts +14 -9
  26. package/dist/core/memory-engine/engine.js +21 -8
  27. package/dist/core/memory-engine/layout.d.ts +31 -0
  28. package/dist/core/memory-engine/layout.js +68 -3
  29. package/dist/core/memory-engine/tools.js +6 -0
  30. package/dist/core/memory-engine/types.d.ts +24 -0
  31. package/dist/core/memory.d.ts +19 -0
  32. package/dist/core/memory.js +12 -0
  33. package/dist/core/remote-env.d.ts +32 -0
  34. package/dist/core/remote-env.js +4 -1
  35. package/dist/core/runner/advertised-writable-dirs.d.ts +59 -1
  36. package/dist/core/runner/advertised-writable-dirs.js +51 -3
  37. package/dist/core/runner/announce-once-ledger.d.ts +15 -0
  38. package/dist/core/runner/announce-once-ledger.js +8 -0
  39. package/dist/core/runner/classifier-ladder.d.ts +5 -0
  40. package/dist/core/runner/classifier-ladder.js +13 -2
  41. package/dist/core/runner/contracts.d.ts +44 -1
  42. package/dist/core/runner/denial-limit-arms.d.ts +14 -1
  43. package/dist/core/runner/denial-limit-arms.js +5 -4
  44. package/dist/core/runner/env-capability-census.d.ts +30 -0
  45. package/dist/core/runner/env-capability-census.js +38 -0
  46. package/dist/core/runner/forward-sink.d.ts +32 -0
  47. package/dist/core/runner/forward-sink.js +41 -0
  48. package/dist/core/runner/gate-exit.d.ts +4 -0
  49. package/dist/core/runner/gate-exit.js +4 -1
  50. package/dist/core/runner/git-status-frame.d.ts +0 -9
  51. package/dist/core/runner/listing-fold.d.ts +57 -0
  52. package/dist/core/runner/listing-fold.js +75 -0
  53. package/dist/core/runner/prepare-execution-env.d.ts +1 -1
  54. package/dist/core/runner/prepare-execution-env.js +2 -0
  55. package/dist/core/runner/prepare-gate-stations.d.ts +6 -0
  56. package/dist/core/runner/prepare-gate-stations.js +1 -3
  57. package/dist/core/runner/prepare-hands-readface.d.ts +4 -0
  58. package/dist/core/runner/prepare-hands-readface.js +3 -5
  59. package/dist/core/runner/prepare-listings.d.ts +3 -0
  60. package/dist/core/runner/prepare-listings.js +4 -2
  61. package/dist/core/runner/prepare-memory.js +7 -1
  62. package/dist/core/runner/prepare-policy-chain.js +5 -5
  63. package/dist/core/runner/prepare-prompt-inputs.d.ts +2 -2
  64. package/dist/core/runner/prepare-run-refs.d.ts +4 -2
  65. package/dist/core/runner/prepare-run-refs.js +5 -4
  66. package/dist/core/runner/prepare-task.js +8 -6
  67. package/dist/core/runner/prepare-wiring-manifest.d.ts +8 -1
  68. package/dist/core/runner/prepare-wiring-manifest.js +5 -2
  69. package/dist/core/runner/run-clock-and-content.js +3 -7
  70. package/dist/core/runner/run-harness-handlers.js +10 -10
  71. package/dist/core/runner/run-leg.js +28 -19
  72. package/dist/core/runner/run-settle-and-teardown.js +2 -5
  73. package/dist/core/runner/run-turn-boundary.js +44 -33
  74. package/dist/core/runner/runtask.js +2 -1
  75. package/dist/core/runner/synthetic-tools.d.ts +13 -39
  76. package/dist/core/runner/synthetic-tools.js +15 -59
  77. package/dist/core/runner/task-root-canonical.d.ts +45 -0
  78. package/dist/core/runner/task-root-canonical.js +18 -0
  79. package/dist/core/runner/turn-attachments.d.ts +125 -49
  80. package/dist/core/runner/turn-attachments.js +124 -66
  81. package/dist/core/runner/write-protection-station.d.ts +59 -0
  82. package/dist/core/runner/write-protection-station.js +32 -0
  83. package/dist/core/safe-notify.d.ts +5 -0
  84. package/dist/core/safe-notify.js +24 -2
  85. package/dist/core/sensitive-path-policy.d.ts +53 -27
  86. package/dist/core/sensitive-path-policy.js +16 -26
  87. package/dist/core/skill-listing-budget.d.ts +34 -40
  88. package/dist/core/skill-listing-budget.js +25 -26
  89. package/dist/core/skills-directory.js +17 -10
  90. package/dist/core/task-registry.js +2 -1
  91. package/dist/core/task-spec.d.ts +8 -7
  92. package/dist/core/tool-policy.d.ts +12 -1
  93. package/dist/core/tool-policy.js +39 -25
  94. package/dist/core/wiring-manifest.d.ts +31 -0
  95. package/dist/core/wiring-manifest.js +1 -0
  96. package/dist/core/write-protect.d.ts +124 -18
  97. package/dist/core/write-protect.js +91 -34
  98. package/dist/index.d.ts +6 -5
  99. package/dist/index.js +6 -5
  100. package/dist/orchestration/workflow-primitives.js +10 -20
  101. package/dist/stores/file/memory-store.d.ts +5 -2
  102. package/dist/stores/file/memory-store.js +8 -4
  103. package/dist/tools/fs/bash-readonly-classifier.d.ts +13 -2
  104. package/dist/tools/fs/bash-readonly-classifier.js +318 -36
  105. package/dist/tools/fs/fs-bash.d.ts +1 -1
  106. package/dist/tools/fs/fs-bash.js +12 -25
  107. package/dist/tools/fs/fs-shared.d.ts +22 -0
  108. package/dist/tools/fs/fs-shared.js +30 -0
  109. package/dist/tools/fs/index.d.ts +1 -1
  110. package/dist/tools/fs/index.js +1 -1
  111. package/package.json +2 -1
  112. package/test/export-surface.snapshot.json +25 -3
  113. package/wire-changes/core@7.20.0.json +568 -0
  114. package/wire-changes/core@7.20.1.json +183 -0
package/dist/index.js CHANGED
@@ -71,7 +71,7 @@ export { getShellConfig, isWslBashLauncher } from "./engine/execution-env/node-e
71
71
  export { isSecretEnvKey, scrubSecretEnv } from "./core/secret-env.js";
72
72
  export { MAX_EXEC_OUTPUT_BYTES, RollingTailBuffer, markTruncated } from "./core/exec-output-tail.js";
73
73
  export { physicalPathOf, physicalPathOfExisting } from "./core/physical-path.js";
74
- export { RemoteExecutionError, hasDestroy, isRemoteExecutionEnv, isSuspendable, isIsolated, missingRestoreSurface, isRetryableRemoteErrorCode, RETRYABLE_REMOTE_ERROR_CODES, } from "./core/remote-env.js";
74
+ export { RemoteExecutionError, hasDestroy, isRemoteExecutionEnv, isSuspendable, isIsolated, canonicalPathIsAuthoritative, missingRestoreSurface, isRetryableRemoteErrorCode, RETRYABLE_REMOTE_ERROR_CODES, } from "./core/remote-env.js";
75
75
  export { withRetry } from "./core/with-retry.js";
76
76
  export { addWorktree, pruneWorktrees, WORKTREE_PARENT } from "./core/git-worktree-env.js";
77
77
  export { runExecGate } from "./core/exec-gate.js";
@@ -83,7 +83,7 @@ export { classifyCompoundReadonlyDetailed, formatOutOfRootReadApprovalOption, }
83
83
  export { resolveBashTimeoutCaps } from "./tools/fs/index.js";
84
84
  export { READ_FACE_DEFAULT_DENY_ENTRIES, READ_FACE_BUILTIN_DENY_TABLE, READ_DENY_BUILTIN_TIERS, READ_DENY_DEFAULT_TIERS, resolveReadDenyBuiltins, compileReadDeny, } from "./tools/fs/index.js";
85
85
  export { deploymentFaceClampNotice, resolveContainmentFace, } from "./tools/fs/index.js";
86
- export { WRITE_PROTECTED_DEFAULT_TABLE, resolveWriteProtectedTable, compileWriteProtection, } from "./core/write-protect.js";
86
+ export { WRITE_PROTECTED_DEFAULT_TABLE, resolveWriteProtectedTable, compileWriteProtection, WRITE_PROTECTION_TARGET_VIEWS, } from "./core/write-protect.js";
87
87
  export { InMemoryToolResultStore, OFFLOAD_TOOL_NAME, DEFAULT_TOOL_RESULT_THRESHOLD_CHARS, assertSafeToolResultRef, buildToolResultRef, toolResultContentSegment, MAX_MINTED_TOOL_RESULT_REF_CHARS, assertToolResultProvenanceMatch, normalizeToolResultProvenance, toolResultProvenanceOf, ToolResultRefConflictError, TOOL_RESULT_REF_CONFLICT_CODE, } from "./core/tool-result-store.js";
88
88
  export { InMemoryCheckpointStore, CheckpointError, mintCheckpointId, mintCheckpointToken, checkpointVersionOf, CURRENT_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, ORG_ADMISSION_CHECKPOINT_VERSION, F012_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, debitLedger, remainingBudgetMicroUsd, remainingTokens, winnerFromOutcome, validatePendingSteer, readPendingSteerQueue, appendPendingSteer, MAX_PENDING_STEER_CHARS, MAX_PENDING_STEER_ENTRIES, PENDING_STEER_QUEUE_BYTE_BUDGET_BYTES, PENDING_STEER_FROZEN_FIELDS, ACTOR_ASSERTION_FROZEN_FIELDS, MAX_ACTOR_FIELD_CHARS, MAX_STEER_INPUT_ID_CHARS, riskSeverity, buildRiskDescriptor, summarizeCheckpoint, realApprovalOrgFact, resolveCheckpointStore, checkpointStoreExecutionUndeclared, } from "./core/checkpoint-store.js";
89
89
  export { EXECUTION_OUTCOME_RECORD_WORDS, EXECUTION_RECORD_LEAVES_ROW_UNRECORDED, ExecutionOutcomeConflictError, checkpointExecutionRecorded, executionRecordDisposition, sameExecutionRecord, executionVerdict, } from "./core/checkpoint-execution-record.js";
@@ -103,8 +103,8 @@ export { FileStorageBackend, FileSessionRepo, FileCheckpointStore, FileMemorySto
103
103
  export { CacheBreakDetector } from "./core/cache-break-detector.js";
104
104
  export { maybeCompact } from "./core/auto-compaction.js";
105
105
  export { brainToRuntime } from "./core/runtime.js";
106
- export { createSensitivePathPolicy, RECOMMENDED_SENSITIVE_PATTERNS, RECOMMENDED_SENSITIVE_ASK_PATTERNS } from "./core/sensitive-path-policy.js";
107
- export { createFsWriteGatePolicy } from "./core/fs-write-gate-policy.js";
106
+ export { createSensitivePathPolicy, RECOMMENDED_SENSITIVE_PATTERNS } from "./core/sensitive-path-policy.js";
107
+ export { createFsWriteGatePolicy, WRITE_EXEMPT_DIR_COVERS_ROOT_CODE } from "./core/fs-write-gate-policy.js";
108
108
  export { RETIRED_TOOL_NAMES } from "./core/tool-name-aliases.js";
109
109
  export { DEFAULT_SUBAGENT_TOOL_NAME } from "./agents/subagent.js";
110
110
  export { renderTaskNotificationXml, taskNotificationDedupKey, isDelegatedAgentTerminal, isTerminalTaskNotification, SystemInjectionQueue, SYSTEM_INJECTION_PRIORITIES, isSystemInjectionPriority, AGENT_MESSAGE_TAG, renderAgentMessageFrame, } from "./core/task-notification.js";
@@ -152,6 +152,7 @@ export {} from "./core/hooks.js";
152
152
  export { SWAPPABLE_DEP_SEATS, SWAP_SEAT_NOTICE, isSwappableDepSeat, screenSwappableDeps } from "./core/swappable-deps.js";
153
153
  export { parseAutoModeResponse, createAutoModeDecider, AUTO_MODE_UNAVAILABLE_CAUSES, isAutoModeUnavailableCause, classifierUnavailableDenyMessage, classifierParseFailureDenyMessage, AUTO_MODE_PARSE_FAILURES, isAutoModeParseFailure, AUTO_MODE_CLASSIFIER_FALLBACKS, isAutoModeClassifierFallback, AUTO_MODE_FALLBACK_CAUSES, isAutoModeFallbackCause, createAutoModeDenialTracker, denialLimitFallbackMessage, denialLimitSentence, unarmedWindow, } from "./core/auto-mode.js";
154
154
  export { AUTO_MODE_DENIAL_LIMIT_DEFAULTS, AUTO_MODE_DENIAL_AUTO_DENY_DEFAULT_MS, AUTO_MODE_CLASSIFIER_MAX_TOKENS } from "./core/auto-mode-defaults.js";
155
+ export { classifierSeatModel } from "./core/runner/classifier-ladder.js";
155
156
  export { buildAutoModePrompt, renderAutoModeWindow, renderAutoModeAction, AUTO_MODE_DEFAULTS_SENTINEL, } from "./core/auto-mode-prompt.js";
156
157
  export { AUTO_MODE_BASE_PROMPT, AUTO_MODE_PERMISSIONS_EXTERNAL } from "./core/auto-mode-prompt-assets.js";
157
158
  export { AUTO_MODE_ARMING_RECIPE_VERSION, autoModeArmingRecipeOf, sanitizeAutoModeArmingRecipe, foldAutoModeArming, } from "./core/auto-mode-arming.js";
@@ -174,7 +175,7 @@ export { AdoptionError, assertAdoptionBootGate, readRootAdoptionFile, writeRootA
174
175
  export { adoptLocalDataRoot, ackAdoptionConfig, witnessAdoptionConfig, listAdoptionQuarantine, readAdoptionStatus, } from "./stores/file/adoption/adopt.js";
175
176
  export { formatHookFeedback, runToolGate, createHookEnvCapabilities, createPreToolUseConstraintPolicy, normalizePersistedRuleHit, } from "./core/hooks.js";
176
177
  export { HOOK_FAMILIES, HOOKS_INVALID_CODE } from "./core/hook-seats.js";
177
- export { InMemoryMemoryStore, composeMemoryBlock, composeLayeredMemoryBlock, normalizeMemorySpec, supportsConsolidation, supportsPeriodicConsolidation, firstSentence, expandLexicalTerms, lexicalSearchMatch, classifyPromotable, enforcePromotableWriteGate, guardedMemoryStore, MemoryGateError, detectSecret, enforceSecretWriteGate, enforceStructuredNoteSecretGate, } from "./core/memory.js";
178
+ export { InMemoryMemoryStore, composeMemoryBlock, composeLayeredMemoryBlock, normalizeMemorySpec, supportsConsolidation, supportsPeriodicConsolidation, firstSentence, expandLexicalTerms, lexicalSearchMatch, classifyPromotable, enforcePromotableWriteGate, guardedMemoryStore, MemoryGateError, MemoryProjectionError, detectSecret, enforceSecretWriteGate, enforceStructuredNoteSecretGate, } from "./core/memory.js";
178
179
  export { MemoryEngine, FileMemoryEngineBackend, memoryBackendContract, assertMemoryBackendSearchEquivalence, buildMemoryInstruction, writableRootOf, truncateIndex, scanEntryFiles, deriveRepoKey, deriveRepoMemoryDir, deriveControlPlaneDir, deriveRepoControlPlaneDir, resolveMemoryEngineRoot, scopeDirName, ControlPlaneCorruptError, parseEntryFile, serializeEntryFile, computeEntryRev, entryFromFile, MEMORY_ORIGIN_CAUSES, committedOriginOf, originEquals, consolidationExposedFrontmatter, ambiguousOriginRepresentation, committedDistilledOf, distilledEquals, CONSOLIDATION_DEFAULTS, ConsolidationRefusedError, MEMORY_SEARCH_SUPERSEDED_TAG, consolidationTypeEligible, deriveSupersededSet, memorySupersededNote, readIntentCredentials, supersessionFuseCeiling, memoryConsolidationRecommendedNotice, memoryConsolidationCommittedNotice, memoryConsolidationConflictNotice, memoryConsolidationIncompleteNotice, memoryConsolidationRefusedNotice, memoryConsolidationWithheldNotice, DISTILLER_DEFAULT_MAX_INPUTS_PER_PRODUCT, LLM_DISTILLER_CONTRACT, LLM_DISTILLER_CONTRACT_DL2, LLM_DISTILLER_CONTRACT_DL3, LLM_DISTILLER_CONTRACTS, MEMORY_DISTILLER_CONTRACT_V1, contractGroupingDiff, driveConsolidationToFixpoint, isAliasModelId, llmPlanDistiller, mintExposurePartitionedPlan, mintLlmConsolidationPlan, MEMORY_DISTILLER_PURITY_CONTRACT_V1, detectCleanArmVerbatimLeak, openAiCompatChatSeat, parseJsonAnswer, planParseRepairs, sanitizeLlmGroups, scheduleUnderFuse, CONSOLIDATION_DRIVER_PLANS_DIR, CONSOLIDATION_DRIVER_RUNS_FILE, CONSOLIDATION_RUN_STOP_REASONS, archiveDistillerPlan, readConsolidationDriverRun, runMemoryConsolidationDriver, isInstructionEntry, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_RECALL_DISCIPLINE, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, MEMORY_CAPTURE_OPTOUT_NOTICE, memoryCaptureOptedOutNotice, memoryCaptureOptOutUnpersistedNotice, SESSION_CAPTURE_OPTOUT_DIR, markSessionCaptureOptOut, readSessionCaptureOptOut, listSessionCaptureOptOut, fileSessionCaptureRecordStore, readV2HeaderHints, LINEAGE_FILE, CHALLENGES_FILE, CHALLENGED_HISTORY_FILE, CHALLENGE_LEDGER_MAX_EVENTS, rebuildStrictControlPlaneLedger, MEMORY_INDEX_FILENAME, MEMORY_INDEX_MAX_LINES, MEMORY_INDEX_MAX_BYTES, MEMORY_SEARCH_TOOL_NAME, MEMORY_GET_TOOL_NAME, MEMORY_INDEX_TOOL_NAME, MEMORY_ENGINE_TOOL_NAMES, MEMORY_EXPOSURE_BANNER, MEMORY_EXPOSURE_HANDLE_TAG, memoryExposureIndexRow, STUB_ARCHIVED_LINE, DEFAULT_MAX_MEMORY_FILES, DEFAULT_MAX_ENTRY_DEPTH, DEFAULT_HARVEST_DEADLINE_MS, DEFAULT_HARVEST_FILE_BUDGET, MASS_DELETION_FUSE_RATIO, scanMemoryWrite, scanMemoryFileName, scanRemediation, MEMORY_FILENAME_SEGMENT_RE, renderAnnouncements, enqueueMemoryAnnouncement, drainMemoryAnnouncements, peekMemoryAnnouncements, bumpScanFuse, scanFuseCount, clearScanFuse, ANNOUNCEMENTS_FILE, MEMORY_ANNOUNCEMENTS_MAX, SCAN_FUSE_FILE, SCAN_FUSE_THRESHOLD, erasureSelectHash, computeMemoryBundleHash, SCOPE_SEGMENT_MAX_ENCODED, PROJECT_MARKER_PATH, encodeScopeSegment, decodeScopeSegment, parseScopeKey, formatUserScope, formatOrgScope, formatProjScope, formatUserProjScope, isPersonalScope, assertScopeContractPlacement, formatProjectMarker, parseProjectMarker, resolveProjectId, PROJECT_ID_REGEX, migrateScope, materializeEntriesToFiles, harvestFilesToPatches, projectionsToWriteBack, screenInboundEntries, REMOTE_HARVEST_PER_FILE_BYTES, REMOTE_MASS_DELETION_FUSE_RATIO, deriveProjectMemoryDir, deriveProjectControlDir, recordProjectIdHint, lookupProjectIdHint, PROJECT_ID_HINTS_FILE, reconcileMemoryEntries, nextSyncBaseline, mergeRecallHits, syncMemoryScope, } from "./core/memory-engine/index.js";
179
180
  export { SHARED_MEMORY_READ_CAP_BYTES, SHARED_MEMORY_LIST_PAGE_SIZE, SharedMemoryStoreError, } from "./core/shared-memory/types.js";
180
181
  export { sharedMemoryStoreContract, } from "./core/shared-memory/contract.js";
@@ -1,4 +1,5 @@
1
1
  import { assertSupportedAgentIsolation } from "./workflow.js";
2
+ import { childClampSeats } from "../core/child-clamp-seats.js";
2
3
  import { WorkflowScriptError } from "./workflow-meta.js";
3
4
  import { buildGovernedChildSpec, emitIgnoredAgentOptionsNotice, overlayWorktreeBaseline, MAX_ANNOUNCED_KEYS, SPEC_KEY_ALIASES, WHITELIST_KEYS, } from "./workflow-governance.js";
4
5
  const ACCEPTED_AGENT_OPTION_KEYS = ["label", "phase", "schema", "agentType", "isolation"];
@@ -77,27 +78,16 @@ export function buildWorkflowPrimitives(ctx, governance, onAgentSpawn, parentChe
77
78
  if (parentCheckpointStore !== undefined && (parentCheckpointStore === "disabled" || childSpec.checkpointStore === undefined)) {
78
79
  childSpec.checkpointStore = parentCheckpointStore;
79
80
  }
80
- if (parentReadFace) {
81
- const pf = parentReadFace();
82
- if (pf === "roots" && childSpec.readFace !== "roots")
83
- childSpec.readFace = "roots";
84
- }
85
- if (parentWriteFace) {
86
- const pw = parentWriteFace();
87
- if (pw === "roots" && childSpec.writeFace !== "roots")
88
- childSpec.writeFace = "roots";
89
- }
90
- if (parentReadDenyPatterns) {
91
- const pd = parentReadDenyPatterns();
92
- if (pd !== undefined && pd.length > 0) {
93
- childSpec.readDenyPatterns = childSpec.readDenyPatterns !== undefined ? [...childSpec.readDenyPatterns, ...pd] : [...pd];
94
- }
95
- }
96
- if (parentHandsReadOnly === true) {
97
- childSpec.handsReadOnly = true;
98
- }
81
+ const pd = parentReadDenyPatterns?.();
82
+ const workflowChildClamps = childClampSeats({
83
+ handsReadOnly: parentHandsReadOnly,
84
+ interactiveTools: parentInteractiveToolsOff === true ? false : undefined,
85
+ readFace: parentReadFace?.() === "roots" ? "roots" : childSpec.readFace,
86
+ writeFace: parentWriteFace?.() === "roots" ? "roots" : childSpec.writeFace,
87
+ readDenyPatterns: pd !== undefined && pd.length > 0 ? [...(childSpec.readDenyPatterns ?? []), ...pd] : childSpec.readDenyPatterns,
88
+ });
89
+ Object.assign(childSpec, workflowChildClamps);
99
90
  if (parentInteractiveToolsOff === true) {
100
- childSpec.interactiveTools = false;
101
91
  if (childSpec.interactionPosture === "interactive")
102
92
  delete childSpec.interactionPosture;
103
93
  }
@@ -1,7 +1,6 @@
1
1
  import { type Embedder, type MemoryNoteHeader, type MemoryNoteRecord, type MemoryStore, type MemoryVectorMode, type ScoredMemory, type StructuredNoteInput } from "../../core/memory.js";
2
2
  export declare class FileMemoryStore implements MemoryStore {
3
3
  private readonly root;
4
- private readonly tmpDir;
5
4
  /** Optional injected embedder (design/81 Slice 5). When present, note bodies are embedded (in the background,
6
5
  * after the durable note write) into a rebuildable sidecar index and `searchScored` ranks by cosine distance
7
6
  * (portable / in-process, mirrors PgMemoryStore's jsonb mode). Absent ⇒ lexical, byte-identical to before. */
@@ -59,7 +58,11 @@ export declare class FileMemoryStore implements MemoryStore {
59
58
  * Only when an embedder is injected; a failure/timeout leaves the row lexical (fail-open). The durable note
60
59
  * write has ALREADY happened before this is called, so this never gates durability. */
61
60
  private queueEmbed;
62
- /** Rewrite the derived `MEMORY.md` projection atomically (CC markdown parity). */
61
+ /** Rewrite the derived `MEMORY.md` projection atomically (CC markdown parity), STAGED IN THE TARGET'S
62
+ * OWN DIRECTORY: `rename(2)` is defined only within one filesystem, so staging beside the target makes
63
+ * the two share a mount by construction where a fixed scratch dir elsewhere made it deployment luck (a
64
+ * memory tree on its own device took `EXDEV` on every note). EVERY caller is POST-DURABLE, so a failure
65
+ * here cannot mean the note was lost: it is {@link MemoryProjectionError}, that phase's one name. */
63
66
  private writeProjection;
64
67
  read(scope: string): string | null;
65
68
  append(scope: string, note: string): string | void;
@@ -1,6 +1,6 @@
1
1
  import { join } from "node:path";
2
2
  import { uuidv7 } from "../../internal/harness.js";
3
- import { firstSentence, lexicalSearchMatch, parseNoteTimestamp, } from "../../core/memory.js";
3
+ import { firstSentence, lexicalSearchMatch, parseNoteTimestamp, MemoryProjectionError, } from "../../core/memory.js";
4
4
  import { cosineDistance, jaccardDistance, termSet } from "../../core/memory-vector.js";
5
5
  import { canonicalStoreKey, AppendLog, atomicWriteFile, ensureDir, readJsonlRecords, sanitizeScope, } from "./fs-atomic.js";
6
6
  import { assertAdoptionBootGate } from "./adoption/marker.js";
@@ -8,7 +8,6 @@ const MAX_OPEN_MEMORY_SCOPES = 64;
8
8
  const sharedMemoryDirs = new Map();
9
9
  export class FileMemoryStore {
10
10
  root;
11
- tmpDir;
12
11
  embedder;
13
12
  embedTimeoutMs;
14
13
  pending = new Set();
@@ -31,7 +30,6 @@ export class FileMemoryStore {
31
30
  this.sharedScopes = { scopes: new Map(), refs: 1, openLogs: new Set() };
32
31
  sharedMemoryDirs.set(this.scopeKey, this.sharedScopes);
33
32
  }
34
- this.tmpDir = join(root, "tmp");
35
33
  this.embedder = opts.embedder;
36
34
  this.embedTimeoutMs = Math.max(0, opts.embedTimeoutMs ?? 10_000);
37
35
  ensureDir(this.root);
@@ -148,7 +146,13 @@ export class FileMemoryStore {
148
146
  }
149
147
  writeProjection(state) {
150
148
  const body = state.entries.map(renderBullet).join("\n");
151
- atomicWriteFile(this.tmpDir, join(state.dir, "MEMORY.md"), body ? `${body}\n` : "");
149
+ const target = join(state.dir, "MEMORY.md");
150
+ try {
151
+ atomicWriteFile(state.dir, target, body ? `${body}\n` : "");
152
+ }
153
+ catch (err) {
154
+ throw new MemoryProjectionError(target, err);
155
+ }
152
156
  }
153
157
  read(scope) {
154
158
  const { entries } = this.scopeState(scope);
@@ -35,7 +35,7 @@ export { NOT_AUTO_ALLOWED } from "./bash-program-position.js";
35
35
  export type { ShellSegmentConnector } from "./bash-lexer.js";
36
36
  export declare const BASH_READONLY_DEFAULT_ALLOW: readonly string[];
37
37
  /**
38
- * The CLASSIFY face's default allow set: the shared list PLUS the verbs that are
38
+ * The verbs the CLASSIFY face admits ON TOP of the shared list ({@link bashClassifyDefaultAllow}) —
39
39
  * admissible only where the compound classifier's per-verb judgment runs, and NOT on the shared list's
40
40
  * other consumer faces:
41
41
  * · `find` — read-only only when no CC `aCy` dangerous predicate token is present (guard arm);
@@ -54,7 +54,18 @@ export declare const BASH_READONLY_DEFAULT_ALLOW: readonly string[];
54
54
  * poll-loop face inherits the guard arms for free: its body vetting delegates to the same compound
55
55
  * classifier.
56
56
  */
57
- export declare const BASH_CLASSIFY_DEFAULT_ALLOW: readonly string[];
57
+ export declare const BASH_CLASSIFY_EXTRA_ALLOW: readonly string[];
58
+ /** A verb only the classify face admits. */
59
+ export type BashClassifyExtraVerb = (typeof BASH_CLASSIFY_EXTRA_ALLOW)[number];
60
+ /**
61
+ * @contract shell.classify_allow.one_union_point — the classify face's default allow set is the union of
62
+ * two SPELLED-OUT tables ({@link BASH_READONLY_DEFAULT_ALLOW} ∪ {@link BASH_CLASSIFY_EXTRA_ALLOW}) and
63
+ * this function is its only assembly point; no call site composes its own. Both halves stay arrays of
64
+ * string literals so a static reader can enumerate them: a vocabulary derived in place
65
+ * (`[...TABLE, "extra"]`) is invisible to the release's wire-change manifest, so a verb added to or
66
+ * dropped from this security-relevant set would move nothing a downstream reader could see.
67
+ */
68
+ export declare function bashClassifyDefaultAllow(): readonly string[];
58
69
  /**
59
70
  * G2 — options for {@link parseLeadingCommandName}. Omitting them keeps the parser byte-identical to
60
71
  * what every existing caller (the `bash_readonly` declaration face, the coarse command-name policy, the
@@ -8,7 +8,10 @@ export const BASH_READONLY_DEFAULT_ALLOW = [
8
8
  "cal", "uptime", "id", "groups", "nproc", "locale", "free", "realpath", "readlink",
9
9
  "true", "false", "seq", "expr", "type", "sleep", "diff", "cmp", "comm",
10
10
  ];
11
- export const BASH_CLASSIFY_DEFAULT_ALLOW = [...BASH_READONLY_DEFAULT_ALLOW, "find", "sed", "cd"];
11
+ export const BASH_CLASSIFY_EXTRA_ALLOW = ["find", "sed", "cd"];
12
+ export function bashClassifyDefaultAllow() {
13
+ return [...BASH_READONLY_DEFAULT_ALLOW, ...BASH_CLASSIFY_EXTRA_ALLOW];
14
+ }
12
15
  const SHELL_OPERATORS = /[;&|<>$()`\n\r\\]/;
13
16
  function hasBareShellOperator(s, quotedOperatorsAreText) {
14
17
  if (!quotedOperatorsAreText)
@@ -304,7 +307,7 @@ function refusedCommandExempt(command, allow, boundary, depth, baseMovedOutside
304
307
  const resolved = resolveOperandLexically(boundary.cwd ?? boundary.roots[0], word, boundary.homeDir);
305
308
  if (resolved === undefined || boundary.denyMatch?.(resolved) != null)
306
309
  return false;
307
- return withinAnyRoot(boundary.roots, resolved);
310
+ return boundary.face === "open" || withinAnyRoot(boundary.roots, resolved);
308
311
  };
309
312
  const unlistedSegmentJudgedInside = (words, from) => {
310
313
  if (boundary === undefined)
@@ -446,18 +449,6 @@ function isGrepPatternFlagToken(tok) {
446
449
  }
447
450
  return /^-[A-Za-z0-9]*[ef]/.test(tok);
448
451
  }
449
- function isGrepPatternPayloadLongOption(tok) {
450
- if (!tok.includes("="))
451
- return false;
452
- const name = longOptionNameOf(tok);
453
- return name !== undefined && isLongOptionAbbrevOf(name, "regexp");
454
- }
455
- function isCutDelimiterPayloadLongOption(tok) {
456
- if (!tok.includes("="))
457
- return false;
458
- const name = longOptionNameOf(tok);
459
- return name !== undefined && (isLongOptionAbbrevOf(name, "delimiter") || isLongOptionAbbrevOf(name, "output-delimiter"));
460
- }
461
452
  function isGrepClusterFileStdin(tok) {
462
453
  if (tok.startsWith("--") || !/^-[A-Za-z0-9]/.test(tok))
463
454
  return false;
@@ -472,6 +463,283 @@ function isGrepClusterFileStdin(tok) {
472
463
  }
473
464
  return false;
474
465
  }
466
+ function valueNamesAFile(kind) {
467
+ return kind === "path" || kind === "indirect" || kind === "form" || kind === "header" || kind === "url";
468
+ }
469
+ function widestValueKind(kinds) {
470
+ if (kinds.has("indirect"))
471
+ return "indirect";
472
+ if (kinds.has("path"))
473
+ return "path";
474
+ if (kinds.has("form"))
475
+ return "form";
476
+ if (kinds.has("header"))
477
+ return "header";
478
+ if (kinds.has("url"))
479
+ return "url";
480
+ return [...kinds][0];
481
+ }
482
+ const VERB_OPTION_SLOTS = {
483
+ head: {
484
+ shortValue: { c: "count", n: "count" },
485
+ shortPlain: "qvz",
486
+ longValue: { bytes: "count", lines: "count" },
487
+ longOptionalValue: {},
488
+ longPlain: ["help", "quiet", "silent", "verbose", "version", "zero-terminated"],
489
+ },
490
+ tail: {
491
+ shortValue: { b: "count", c: "count", n: "count", s: "count" },
492
+ shortPlain: "fFqvz",
493
+ longValue: { bytes: "count", lines: "count", "max-unchanged-stats": "count", pid: "count", "sleep-interval": "count" },
494
+ longOptionalValue: { follow: "label" },
495
+ longPlain: ["help", "quiet", "retry", "silent", "verbose", "version", "zero-terminated"],
496
+ },
497
+ cut: {
498
+ shortValue: { b: "count", c: "count", d: "delimiter", f: "count" },
499
+ shortPlain: "nsz",
500
+ longValue: { bytes: "count", characters: "count", delimiter: "delimiter", fields: "count", "output-delimiter": "delimiter" },
501
+ longOptionalValue: {},
502
+ longPlain: ["complement", "help", "only-delimited", "version", "zero-terminated"],
503
+ },
504
+ grep: {
505
+ shortValue: { A: "count", B: "count", C: "count", D: "label", d: "label", e: "pattern", f: "path", m: "count" },
506
+ shortPlain: "0123456789abcEFGhHiIlLnoPqrRsUvVwxyzZ",
507
+ longValue: {
508
+ "after-context": "count",
509
+ "before-context": "count",
510
+ "binary-files": "label",
511
+ devices: "label",
512
+ directories: "label",
513
+ exclude: "name-pattern",
514
+ "exclude-dir": "name-pattern",
515
+ "exclude-from": "path",
516
+ file: "path",
517
+ "group-separator": "label",
518
+ include: "name-pattern",
519
+ label: "label",
520
+ "max-count": "count",
521
+ regexp: "pattern",
522
+ },
523
+ longOptionalValue: { color: "label", colour: "label", context: "count" },
524
+ longPlain: [
525
+ "basic-regexp", "byte-offset", "count", "dereference-recursive", "extended-regexp",
526
+ "files-with-matches", "files-without-match", "fixed-strings", "help", "ignore-case", "initial-tab",
527
+ "invert-match", "line-buffered", "line-number", "line-regexp", "no-filename", "no-group-separator",
528
+ "no-messages", "null", "null-data", "only-matching", "perl-regexp", "quiet", "recursive", "silent",
529
+ "text", "version", "with-filename", "word-regexp",
530
+ ],
531
+ },
532
+ diff: {
533
+ shortValue: { C: "count", D: "label", F: "pattern", I: "pattern", L: "label", S: "path", U: "count", W: "count", X: "path", x: "name-pattern" },
534
+ shortPlain: "abBcdeEHilnNpqrstTuvwyZ",
535
+ longValue: {
536
+ exclude: "name-pattern",
537
+ "exclude-from": "path",
538
+ "from-file": "path",
539
+ "horizon-lines": "count",
540
+ ifdef: "label",
541
+ "ignore-matching-lines": "pattern",
542
+ label: "label",
543
+ "show-function-line": "pattern",
544
+ "starting-file": "path",
545
+ tabsize: "count",
546
+ "to-file": "path",
547
+ width: "count",
548
+ },
549
+ longOptionalValue: { color: "label", context: "count", unified: "count" },
550
+ longPlain: [
551
+ "brief", "ed", "expand-tabs", "help", "ignore-all-space", "ignore-blank-lines", "ignore-case",
552
+ "ignore-space-change", "ignore-tab-expansion", "ignore-trailing-space", "initial-tab", "minimal",
553
+ "new-file", "normal", "paginate", "recursive", "report-identical-files", "rcs", "show-c-function",
554
+ "side-by-side", "speed-large-files", "suppress-blank-empty", "text", "version",
555
+ ],
556
+ },
557
+ cmp: {
558
+ shortValue: { i: "count", n: "count" },
559
+ shortPlain: "blsv",
560
+ longValue: { bytes: "count", "ignore-initial": "count" },
561
+ longOptionalValue: {},
562
+ longPlain: ["help", "print-bytes", "quiet", "silent", "verbose", "version"],
563
+ },
564
+ sed: {
565
+ shortValue: { e: "script", f: "path" },
566
+ shortPlain: "nrEszu",
567
+ longValue: { expression: "script", file: "path", "line-length": "count" },
568
+ longOptionalValue: { "in-place": "label" },
569
+ longPlain: [
570
+ "debug", "follow-symlinks", "help", "null-data", "posix", "quiet", "regexp-extended", "sandbox",
571
+ "separate", "silent", "unbuffered", "version", "zero-terminated",
572
+ ],
573
+ },
574
+ date: {
575
+ shortValue: { d: "count", f: "path", r: "path", s: "count" },
576
+ shortPlain: "uR",
577
+ longValue: { date: "count", file: "path", reference: "path", "rfc-3339": "count", set: "count" },
578
+ longOptionalValue: { "iso-8601": "label" },
579
+ longPlain: ["debug", "help", "rfc-822", "rfc-email", "universal", "utc", "version"],
580
+ },
581
+ tar: {
582
+ shortValue: { b: "count", C: "path", f: "path", T: "path", X: "path" },
583
+ shortPlain: "acdhjJkmOpPrStuvwWxzZ",
584
+ longValue: {
585
+ "blocking-factor": "count",
586
+ directory: "path",
587
+ exclude: "name-pattern",
588
+ "exclude-from": "path",
589
+ file: "path",
590
+ "files-from": "path",
591
+ "strip-components": "count",
592
+ },
593
+ longOptionalValue: {},
594
+ longPlain: [
595
+ "absolute-names", "append", "create", "dereference", "extract", "get", "help", "interactive",
596
+ "keep-old-files", "list", "preserve-permissions", "sparse", "to-stdout", "touch", "update",
597
+ "verbose", "verify", "version",
598
+ ],
599
+ },
600
+ curl: {
601
+ shortValue: {
602
+ A: "label", b: "indirect", C: "count", c: "path", D: "path", d: "indirect", E: "path", e: "label",
603
+ F: "form", H: "header", K: "path", m: "count", o: "path", P: "label", Q: "label", r: "count",
604
+ T: "path", t: "label", U: "label", u: "label", w: "indirect", X: "label", x: "label", Y: "count",
605
+ y: "count", z: "path",
606
+ },
607
+ shortPlain: "#0123456aBfgGhiIjJklLMnNOpqRsSvVZ",
608
+ longValue: {
609
+ cacert: "path",
610
+ capath: "path",
611
+ cert: "path",
612
+ config: "path",
613
+ "connect-timeout": "count",
614
+ cookie: "indirect",
615
+ "cookie-jar": "path",
616
+ data: "indirect",
617
+ "data-ascii": "indirect",
618
+ "data-binary": "indirect",
619
+ "data-raw": "label",
620
+ "data-urlencode": "form",
621
+ "dump-header": "path",
622
+ form: "form",
623
+ "form-string": "label",
624
+ header: "header",
625
+ key: "path",
626
+ "max-time": "count",
627
+ "netrc-file": "path",
628
+ output: "path",
629
+ proxy: "label",
630
+ range: "count",
631
+ referer: "label",
632
+ request: "label",
633
+ resolve: "label",
634
+ trace: "path",
635
+ "time-cond": "path",
636
+ "trace-ascii": "path",
637
+ "upload-file": "path",
638
+ url: "url",
639
+ user: "label",
640
+ "user-agent": "label",
641
+ "write-out": "indirect",
642
+ },
643
+ longOptionalValue: {},
644
+ longPlain: [
645
+ "append", "compressed", "disable", "fail", "get", "globoff", "head", "help", "include", "insecure",
646
+ "junk-session-cookies", "list-only", "location", "manual", "netrc", "next", "no-buffer", "parallel",
647
+ "proxytunnel", "remote-header-name", "remote-name", "remote-time", "show-error", "silent",
648
+ "use-ascii", "verbose", "version",
649
+ ],
650
+ },
651
+ };
652
+ function readVerbOptionSlot(name, tok) {
653
+ const row = VERB_OPTION_SLOTS[name];
654
+ if (row === undefined)
655
+ return undefined;
656
+ if (!tok.startsWith("-") || tok === "-" || tok === "--")
657
+ return { form: "unexplained" };
658
+ if (tok.startsWith("--")) {
659
+ const long = longOptionNameOf(tok);
660
+ if (long === undefined)
661
+ return { form: "unexplained" };
662
+ const eq = tok.indexOf("=");
663
+ const attached = eq >= 0 ? tok.slice(eq + 1) : undefined;
664
+ const withValue = (kind) => attached !== undefined ? { form: "attached", value: attached, kind } : { form: "separated", kind };
665
+ const exact = row.longValue[long];
666
+ if (exact !== undefined)
667
+ return withValue(exact);
668
+ const exactOptional = row.longOptionalValue[long];
669
+ if (exactOptional !== undefined)
670
+ return attached !== undefined ? { form: "attached", value: attached, kind: exactOptional } : { form: "no-value" };
671
+ if (row.longPlain.includes(long))
672
+ return attached === undefined ? { form: "no-value" } : { form: "unexplained" };
673
+ const valueNames = [
674
+ ...Object.keys(row.longValue),
675
+ ...(attached !== undefined ? Object.keys(row.longOptionalValue) : []),
676
+ ].filter((full) => isLongOptionAbbrevOf(long, full));
677
+ const plainNames = [
678
+ ...row.longPlain,
679
+ ...(attached === undefined ? Object.keys(row.longOptionalValue) : []),
680
+ ].filter((full) => isLongOptionAbbrevOf(long, full));
681
+ if (valueNames.length === 0)
682
+ return plainNames.length > 0 && attached === undefined ? { form: "no-value" } : { form: "unexplained" };
683
+ const kinds = new Set(valueNames.map((full) => (row.longValue[full] ?? row.longOptionalValue[full])));
684
+ if (attached !== undefined)
685
+ return { form: "attached", value: attached, kind: widestValueKind(kinds) };
686
+ if (plainNames.length > 0 || kinds.size !== 1)
687
+ return { form: "unexplained" };
688
+ return withValue([...kinds][0]);
689
+ }
690
+ for (let i = 1; i < tok.length; i++) {
691
+ const ch = tok[i];
692
+ const kind = row.shortValue[ch];
693
+ if (kind !== undefined) {
694
+ const rest = tok.slice(i + 1);
695
+ return rest !== "" ? { form: "attached", value: rest, kind } : { form: "separated", kind };
696
+ }
697
+ if (!row.shortPlain.includes(ch))
698
+ return { form: "unexplained" };
699
+ }
700
+ return { form: "no-value" };
701
+ }
702
+ function localFileUrlPath(value) {
703
+ const m = /^file:(?:\/\/[^/?#]*)?(\/[^?#]*)/i.exec(value.trim());
704
+ if (m === null)
705
+ return undefined;
706
+ const raw = m[1];
707
+ try {
708
+ return decodeURIComponent(raw);
709
+ }
710
+ catch {
711
+ return raw;
712
+ }
713
+ }
714
+ function formSpecFileNames(value) {
715
+ const out = [];
716
+ const after = (s) => {
717
+ const at = s.search(/[@<]/);
718
+ if (at !== -1 && at < s.length - 1)
719
+ out.push(s.slice(at + 1));
720
+ };
721
+ after(value);
722
+ for (const part of value.split(/[;,]/))
723
+ after(part);
724
+ return [...new Set(out)];
725
+ }
726
+ function slotValueCandidates(value, kind) {
727
+ if (!valueNamesAFile(kind))
728
+ return [];
729
+ if (kind === "url") {
730
+ const local = localFileUrlPath(value);
731
+ return local !== undefined ? [local] : [];
732
+ }
733
+ if (kind === "form")
734
+ return formSpecFileNames(value).map(literalTildeSpelling);
735
+ const named = value.startsWith("@") && value.length > 1 ? literalTildeSpelling(value.slice(1)) : undefined;
736
+ if (kind === "header")
737
+ return named !== undefined ? [value, named] : [];
738
+ const out = [value];
739
+ if (kind === "indirect" && named !== undefined)
740
+ out.push(named);
741
+ return out;
742
+ }
475
743
  const SEPARATED_VALUE_OPTIONS = {
476
744
  head: { short: "cn", long: ["bytes", "lines"] },
477
745
  tail: { short: "cns", long: ["bytes", "lines", "sleep-interval", "pid", "max-unchanged-stats"] },
@@ -1248,17 +1516,6 @@ function isGrepFileStdinLongOption(tok) {
1248
1516
  return name !== undefined && isLongOptionAbbrevOf(name, "file");
1249
1517
  }
1250
1518
  const CLUSTER_OPTION_CHAR = /[A-Za-z0-9]/;
1251
- function grepClusterValueOwner(tok) {
1252
- if (!tok.startsWith("-") || tok.startsWith("--") || !CLUSTER_OPTION_CHAR.test(tok[1] ?? ""))
1253
- return undefined;
1254
- for (const ch of tok.slice(1)) {
1255
- if (ch === "e" || ch === "f")
1256
- return ch;
1257
- if (!CLUSTER_OPTION_CHAR.test(ch))
1258
- return undefined;
1259
- }
1260
- return undefined;
1261
- }
1262
1519
  function attachedOptionPayloads(tok) {
1263
1520
  const literal = literalTildeSpelling;
1264
1521
  const out = [];
@@ -1282,6 +1539,7 @@ function attachedOptionPayloads(tok) {
1282
1539
  }
1283
1540
  function collectSegmentBoundaryFindings(tokens, boundary) {
1284
1541
  const name = tokens.folded[0];
1542
+ const slotVerb = tokens.slotVerb ?? name;
1285
1543
  if (NO_PATH_OPERAND_COMMANDS.has(name))
1286
1544
  return [];
1287
1545
  const args = tokens.folded.slice(1);
@@ -1328,6 +1586,8 @@ function collectSegmentBoundaryFindings(tokens, boundary) {
1328
1586
  const eoo = args.indexOf("--");
1329
1587
  const patternSuppliedByFlag = name === "grep" && (eoo === -1 ? args : args.slice(0, eoo)).some(isGrepPatternFlagToken);
1330
1588
  let sawOperand = false;
1589
+ const argvOperandsStartAt = tokens.slotVerb !== undefined ? 1 : 0;
1590
+ let sawArgvOperand = false;
1331
1591
  let endOfOptions = false;
1332
1592
  for (let k = 0; k < args.length; k++) {
1333
1593
  const t = args[k];
@@ -1338,15 +1598,35 @@ function collectSegmentBoundaryFindings(tokens, boundary) {
1338
1598
  endOfOptions = true;
1339
1599
  continue;
1340
1600
  }
1341
- if (name === "cut" && (t === "-d" || t === "--delimiter" || t === "--output-delimiter")) {
1342
- k++;
1343
- continue;
1344
- }
1345
- if (name === "cut" && (/^-d./.test(t) || isCutDelimiterPayloadLongOption(t)))
1346
- continue;
1347
- if (name === "grep" && (grepClusterValueOwner(t) === "e" || isGrepPatternPayloadLongOption(t)))
1348
- continue;
1349
1601
  if (t.startsWith("-") && t !== "-") {
1602
+ const slot = readVerbOptionSlot(slotVerb, t);
1603
+ if (sawArgvOperand) {
1604
+ judgeOperand(t, argGlobs(k));
1605
+ if (slot !== undefined && slot.form === "separated") {
1606
+ const ahead = args[k + 1];
1607
+ if (ahead !== undefined && !expandingArgs.has(k + 1)) {
1608
+ for (const c of slotValueCandidates(ahead, slot.kind))
1609
+ judgeOperand(c, argGlobs(k + 1));
1610
+ }
1611
+ }
1612
+ }
1613
+ if (slot !== undefined && slot.form !== "unexplained" && !(slot.form === "separated" && sawArgvOperand)) {
1614
+ if (slot.form === "attached") {
1615
+ for (const c of slotValueCandidates(literalTildeSpelling(slot.value), slot.kind))
1616
+ judgeOperand(c, argGlobs(k));
1617
+ }
1618
+ else if (slot.form === "separated") {
1619
+ const value = args[++k];
1620
+ if (value !== undefined && !expandingArgs.has(k)) {
1621
+ const named = slotValueCandidates(value, slot.kind);
1622
+ for (const c of named)
1623
+ judgeOperand(c, argGlobs(k));
1624
+ if (named.length > 0)
1625
+ positionalOperands.push(value);
1626
+ }
1627
+ }
1628
+ continue;
1629
+ }
1350
1630
  for (const payload of attachedOptionPayloads(t))
1351
1631
  judgeOperand(payload, argGlobs(k));
1352
1632
  continue;
@@ -1354,6 +1634,8 @@ function collectSegmentBoundaryFindings(tokens, boundary) {
1354
1634
  }
1355
1635
  const isGrepPatternSlot = name === "grep" && !patternSuppliedByFlag && !sawOperand;
1356
1636
  sawOperand = true;
1637
+ if (k >= argvOperandsStartAt)
1638
+ sawArgvOperand = true;
1357
1639
  if (t === "-") {
1358
1640
  if (isGrepPatternSlot)
1359
1641
  continue;
@@ -1584,7 +1866,7 @@ export function classifyCompoundReadonlyDetailed(command, allow, boundary, opts)
1584
1866
  const unlisted = tokenizeSegment(segments[si]);
1585
1867
  if (unlisted.folded.length === 0)
1586
1868
  continue;
1587
- foldedSegments.push({ folded: [LAUNCHER_SCAN_HEAD, ...unlisted.folded], raw: [LAUNCHER_SCAN_HEAD, ...unlisted.raw] });
1869
+ foldedSegments.push({ folded: [LAUNCHER_SCAN_HEAD, ...unlisted.folded], raw: [LAUNCHER_SCAN_HEAD, ...unlisted.raw], slotVerb: programBasename(unlisted.folded[0]) });
1588
1870
  pushReach();
1589
1871
  continue;
1590
1872
  }
@@ -1734,7 +2016,7 @@ function evaluateReadBoundary(foldedSegments, boundary, mintGrantCandidate, reac
1734
2016
  if (si > 0 && reach?.[si]?.andJoined !== true)
1735
2017
  andRunStart = reach?.[si]?.orBroken === true ? si + 1 : si;
1736
2018
  const reachable = candidates.filter((c) => c.skippedCd < andRunStart);
1737
- const row = findingsRowFor(JSON.stringify([toks.folded, toks.raw]));
2019
+ const row = findingsRowFor(JSON.stringify([toks.folded, toks.raw, toks.slotVerb]));
1738
2020
  for (const { base: currentBase } of reachable) {
1739
2021
  for (const finding of findingsFor(row, toks, currentBase)) {
1740
2022
  if (finding.kind === "unresolvable") {
@@ -1929,7 +2211,7 @@ export function classifyOutOfRootReadGate(command, allow, boundary) {
1929
2211
  if (UNMODELLED_BASE_MOVERS.has(name))
1930
2212
  baseMoved = true;
1931
2213
  if (!allow.has(name) && name !== "cd") {
1932
- pushReader({ folded: [LAUNCHER_SCAN_HEAD, ...toks.folded.slice(i)], raw: [LAUNCHER_SCAN_HEAD, ...toks.raw.slice(i)] });
2214
+ pushReader({ folded: [LAUNCHER_SCAN_HEAD, ...toks.folded.slice(i)], raw: [LAUNCHER_SCAN_HEAD, ...toks.raw.slice(i)], slotVerb: name });
1933
2215
  continue;
1934
2216
  }
1935
2217
  pushReader(i === 0 && name === written ? toks : { folded: [name, ...toks.folded.slice(i + 1)], raw: [toks.raw[i] ?? name, ...toks.raw.slice(i + 1)] });
@@ -100,7 +100,7 @@ export declare function bashReversibilityProbe(allow?: readonly string[], bounda
100
100
  * add a mandate to an ask, never originate one). The poll-loop face's structured verdict
101
101
  * ({@link classifyBoundedReadonlyPollLoopDetailed}) is read the same three ways as the compound face's.
102
102
  *
103
- * The reader set is the engine's classify set ({@link BASH_CLASSIFY_DEFAULT_ALLOW}) — here it is NOT an
103
+ * The reader set is the engine's classify set ({@link bashClassifyDefaultAllow}) — here it is NOT an
104
104
  * auto-allow list but the set of programs whose operands the faces read; a wider set means more reads are
105
105
  * judged, never that more runs are vouched.
106
106
  *