@sema-agent/core 7.15.0 → 7.17.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 +202 -0
- package/dist/agents/builtin-agents.d.ts +3 -3
- package/dist/agents/cross-session-envelope.d.ts +0 -1
- package/dist/agents/cross-session-envelope.js +0 -1
- package/dist/agents/peer-notices.d.ts +0 -9
- package/dist/agents/peer-notices.js +0 -63
- package/dist/agents/subagent.js +7 -15
- package/dist/brain/request-params.d.ts +2 -2
- package/dist/config/defaults.d.ts +0 -7
- package/dist/config/defaults.js +0 -2
- package/dist/core/artifact-host.d.ts +218 -0
- package/dist/core/artifact-host.js +92 -0
- package/dist/core/ask-origin.d.ts +29 -3
- package/dist/core/ask-origin.js +17 -9
- package/dist/core/ask-question.d.ts +25 -0
- package/dist/core/ask-question.js +4 -0
- package/dist/core/ask-unresolvable-notice.d.ts +30 -21
- package/dist/core/ask-unresolvable-notice.js +16 -8
- package/dist/core/auto-compaction.d.ts +10 -10
- package/dist/core/auto-mode-arming.d.ts +2 -2
- package/dist/core/background-agent-store.d.ts +5 -5
- package/dist/core/background-shell.d.ts +1 -1
- package/dist/core/checkpoint-execution-record.d.ts +0 -3
- package/dist/core/checkpoint-execution-record.js +0 -4
- package/dist/core/checkpoint-store.d.ts +71 -99
- package/dist/core/checkpoint-store.js +9 -16
- package/dist/core/context-edit.d.ts +36 -104
- package/dist/core/context-edit.js +37 -60
- package/dist/core/effective-path-target.d.ts +68 -8
- package/dist/core/effective-path-target.js +42 -30
- package/dist/core/engine-notice.d.ts +46 -32
- package/dist/core/env-budget.d.ts +31 -0
- package/dist/core/env-budget.js +39 -0
- package/dist/core/exec-output-tail.d.ts +5 -5
- package/dist/core/file-history-store.js +3 -6
- package/dist/core/fs-write-gate-policy.d.ts +1 -1
- package/dist/core/fs-write-gate-policy.js +3 -3
- package/dist/core/gate-lanes.js +9 -15
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +4 -0
- package/dist/core/hands-band.d.ts +1 -1
- package/dist/core/hooks.d.ts +18 -17
- package/dist/core/lsp-session.d.ts +1 -1
- package/dist/core/lsp.d.ts +1 -1
- package/dist/core/lsp.js +5 -4
- package/dist/core/mcp.d.ts +5 -5
- package/dist/core/mcp.js +4 -48
- package/dist/core/memory-engine/delegation-settlement.d.ts +37 -12
- package/dist/core/memory-engine/delegation-settlement.js +128 -39
- package/dist/core/memory-engine/engine.d.ts +9 -8
- package/dist/core/memory-engine/engine.js +115 -120
- package/dist/core/memory-engine/file-backend.d.ts +17 -5
- package/dist/core/memory-engine/file-backend.js +78 -34
- package/dist/core/memory-engine/index.d.ts +2 -2
- package/dist/core/memory-engine/index.js +2 -2
- package/dist/core/memory-engine/layout.d.ts +152 -54
- package/dist/core/memory-engine/layout.js +441 -104
- package/dist/core/memory-engine/types.d.ts +8 -3
- package/dist/core/memory-engine/types.js +5 -0
- package/dist/core/memory-recall.d.ts +0 -8
- package/dist/core/memory.d.ts +1 -1
- package/dist/core/oracle-isolation.js +2 -2
- package/dist/core/parent-spec-seat.d.ts +66 -0
- package/dist/core/parent-spec-seat.js +47 -0
- package/dist/core/permission-rule-model.d.ts +11 -6
- package/dist/core/permission-rule-model.js +53 -37
- package/dist/core/permission-rules.js +8 -3
- package/dist/core/protocol-naming.d.ts +2 -2
- package/dist/core/remote-env.d.ts +1 -1
- package/dist/core/retired-keys.d.ts +19 -0
- package/dist/core/retired-keys.js +15 -0
- package/dist/core/runner/abort-race.d.ts +3 -3
- package/dist/core/runner/active-skill-scope.d.ts +2 -2
- package/dist/core/runner/active-skill-scope.js +3 -3
- package/dist/core/runner/assemble-result.d.ts +42 -40
- package/dist/core/runner/assemble-result.js +5 -4
- package/dist/core/runner/checkpoint-scope.d.ts +4 -4
- package/dist/core/runner/clock-and-limits.d.ts +3 -3
- package/dist/core/runner/compaction-call-options.d.ts +35 -99
- package/dist/core/runner/compaction-knobs.d.ts +4 -4
- package/dist/core/runner/compaction-seams.d.ts +5 -5
- package/dist/core/runner/contracts.d.ts +232 -233
- package/dist/core/runner/decide-continuation.d.ts +6 -6
- package/dist/core/runner/denial-limit-arms.d.ts +8 -8
- package/dist/core/runner/derived-route-fallback.d.ts +2 -2
- package/dist/core/runner/gate-exit.d.ts +6 -6
- package/dist/core/runner/gate-exit.js +15 -1
- package/dist/core/runner/git-status-frame.d.ts +7 -7
- package/dist/core/runner/halt-attribution.d.ts +35 -0
- package/dist/core/runner/halt-attribution.js +16 -0
- package/dist/core/runner/inherited-ask-grants.d.ts +6 -6
- package/dist/core/runner/memory-capture-optout.d.ts +1 -1
- package/dist/core/runner/memory-consolidation-driver.d.ts +4 -4
- package/dist/core/runner/memory-consolidation.d.ts +6 -8
- package/dist/core/runner/memory-consolidation.js +0 -1
- package/dist/core/runner/park-commit.d.ts +6 -6
- package/dist/core/runner/permission-rule-lanes.d.ts +19 -19
- package/dist/core/runner/prepare-acquire-reconcile.d.ts +4 -4
- package/dist/core/runner/prepare-artifact.d.ts +53 -0
- package/dist/core/runner/prepare-artifact.js +77 -0
- package/dist/core/runner/prepare-ask-lane.d.ts +3 -3
- package/dist/core/runner/prepare-boundary-parks.d.ts +3 -3
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +9 -7
- package/dist/core/runner/prepare-caps-and-workflow.js +2 -3
- package/dist/core/runner/prepare-config-doors.d.ts +24 -24
- package/dist/core/runner/prepare-config-doors.js +5 -2
- package/dist/core/runner/prepare-context-lane.d.ts +8 -8
- package/dist/core/runner/prepare-context-lane.js +2 -13
- package/dist/core/runner/prepare-defer-classify.d.ts +2 -2
- package/dist/core/runner/prepare-delegation-surface.d.ts +1 -1
- package/dist/core/runner/prepare-file-history.d.ts +3 -3
- package/dist/core/runner/prepare-file-history.js +3 -52
- package/dist/core/runner/prepare-gate-stations.d.ts +5 -5
- package/dist/core/runner/prepare-hands-readface.d.ts +35 -38
- package/dist/core/runner/prepare-hands-readface.js +3 -2
- package/dist/core/runner/prepare-inherited-gate.d.ts +9 -6
- package/dist/core/runner/prepare-inherited-gate.js +7 -8
- package/dist/core/runner/prepare-listings.d.ts +3 -3
- package/dist/core/runner/prepare-lsp.d.ts +3 -3
- package/dist/core/runner/prepare-memory-engine-session.d.ts +5 -5
- package/dist/core/runner/prepare-memory.d.ts +4 -6
- package/dist/core/runner/prepare-memory.js +10 -9
- package/dist/core/runner/prepare-offload-wrappers.d.ts +2 -2
- package/dist/core/runner/prepare-park-ask.d.ts +6 -6
- package/dist/core/runner/prepare-policy-chain.d.ts +7 -7
- package/dist/core/runner/prepare-policy-chain.js +15 -30
- package/dist/core/runner/prepare-prompt-assembly.js +1 -1
- package/dist/core/runner/prepare-prompt-inputs.d.ts +5 -5
- package/dist/core/runner/prepare-protocol-tools.d.ts +1 -1
- package/dist/core/runner/prepare-question-face.d.ts +4 -4
- package/dist/core/runner/prepare-run-refs.d.ts +11 -11
- package/dist/core/runner/prepare-safety-scan.d.ts +2 -2
- package/dist/core/runner/prepare-suspend-saga.d.ts +4 -4
- package/dist/core/runner/prepare-suspend-saga.js +0 -2
- package/dist/core/runner/prepare-task.d.ts +12 -12
- package/dist/core/runner/prepare-task.js +10 -9
- package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +2 -3
- package/dist/core/runner/prepare-wiring-manifest.d.ts +4 -4
- package/dist/core/runner/prepare-wiring-manifest.js +9 -7
- package/dist/core/runner/prepare-workspace-restore.d.ts +1 -1
- package/dist/core/runner/remote-env-retry.d.ts +3 -3
- package/dist/core/runner/resume-admission.d.ts +1 -1
- package/dist/core/runner/resume-admission.js +2 -1
- package/dist/core/runner/resume-claim.d.ts +1 -1
- package/dist/core/runner/resume-internals-and-config.d.ts +1 -1
- package/dist/core/runner/resume-internals-and-config.js +4 -0
- package/dist/core/runner/resume-policy-outcome.d.ts +1 -1
- package/dist/core/runner/resume-preflight.d.ts +1 -1
- package/dist/core/runner/resume-review-outcome.d.ts +1 -1
- package/dist/core/runner/retired-runner-deps.d.ts +3 -0
- package/dist/core/runner/retired-runner-deps.js +7 -0
- package/dist/core/runner/run-brain-sinks.d.ts +1 -1
- package/dist/core/runner/run-clock-and-content.d.ts +4 -4
- package/dist/core/runner/run-compaction-machinery.d.ts +3 -3
- package/dist/core/runner/run-git-lane.d.ts +1 -1
- package/dist/core/runner/run-harness-handlers.d.ts +2 -2
- package/dist/core/runner/run-harness-handlers.js +2 -3
- package/dist/core/runner/run-identity-wiring.d.ts +4 -4
- package/dist/core/runner/run-leg.d.ts +3 -3
- package/dist/core/runner/run-notification-lane.d.ts +2 -2
- package/dist/core/runner/run-reasoning-seat.d.ts +2 -2
- package/dist/core/runner/run-settle-and-teardown.d.ts +5 -5
- package/dist/core/runner/run-settle-and-teardown.js +3 -1
- package/dist/core/runner/run-telemetry-and-budget-seats.d.ts +2 -2
- package/dist/core/runner/run-terminal-adoption.d.ts +5 -5
- package/dist/core/runner/runtask.d.ts +14 -14
- package/dist/core/runner/session-file-state-replay.d.ts +3 -3
- package/dist/core/runner/session-file-state-replay.js +3 -3
- package/dist/core/runner/session-rule-policy.d.ts +2 -2
- package/dist/core/runner/session-rule-policy.js +3 -4
- package/dist/core/runner/steer-admission.d.ts +2 -2
- package/dist/core/runner/stream-halt-verbs.d.ts +1 -1
- package/dist/core/runner/stream-halt-verbs.js +1 -1
- package/dist/core/runner/stream-reap.d.ts +1 -1
- package/dist/core/runner/stream-steer-verb.js +1 -4
- package/dist/core/runner/synthetic-tools.d.ts +8 -8
- package/dist/core/runner/teardown-bounded.d.ts +2 -2
- package/dist/core/runner/tool-defer-gate.d.ts +1 -1
- package/dist/core/runner/tool-disclosure.d.ts +33 -33
- package/dist/core/runner/tool-end-body.d.ts +3 -3
- package/dist/core/runner/tool-face-overlay.d.ts +2 -2
- package/dist/core/runner/tool-output-projection.d.ts +2 -2
- package/dist/core/runner/turn-attachments.d.ts +53 -53
- package/dist/core/runner/usage-accounting.d.ts +4 -4
- package/dist/core/runner/workspace-path.d.ts +1 -1
- package/dist/core/runner/workspace-path.js +3 -0
- package/dist/core/runner-deps.d.ts +30 -49
- package/dist/core/safe-notify.d.ts +1 -1
- package/dist/core/sensitive-path-policy.d.ts +1 -1
- package/dist/core/sensitive-path-policy.js +4 -4
- package/dist/core/session.d.ts +2 -2
- package/dist/core/skill-tool-specifier.d.ts +1 -1
- package/dist/core/skills-directory.d.ts +1 -1
- package/dist/core/store-contracts/checkpoint-store-contract.js +1 -1
- package/dist/core/strategy-store.d.ts +8 -11
- package/dist/core/task-event.d.ts +20 -17
- package/dist/core/task-limits.d.ts +1 -1
- package/dist/core/task-notification.d.ts +5 -5
- package/dist/core/task-registry-agent.d.ts +5 -5
- package/dist/core/task-registry-monitor.d.ts +2 -2
- package/dist/core/task-registry-shared.d.ts +27 -27
- package/dist/core/task-registry-shared.js +3 -13
- package/dist/core/task-registry.d.ts +8 -8
- package/dist/core/task-result.d.ts +37 -37
- package/dist/core/task-spec.d.ts +5 -5
- package/dist/core/task-stream.d.ts +16 -11
- package/dist/core/tool-catalog-entries.d.ts +1 -0
- package/dist/core/tool-catalog-entries.js +5 -0
- package/dist/core/tool-conformance.d.ts +0 -2
- package/dist/core/tool-conformance.js +1 -3
- package/dist/core/tool-errors.d.ts +5 -0
- package/dist/core/tool-errors.js +1 -0
- package/dist/core/tool-policy.d.ts +51 -22
- package/dist/core/tool-policy.js +28 -5
- package/dist/core/tool-result-budget.d.ts +1 -1
- package/dist/core/tool-result-store.d.ts +11 -11
- package/dist/core/tool-spec.d.ts +34 -38
- package/dist/core/tools.d.ts +2 -2
- package/dist/core/trace.d.ts +12 -15
- package/dist/core/untrusted-text.d.ts +1 -1
- package/dist/core/wiring-manifest.d.ts +4 -3
- package/dist/core/workflow-journal-store.d.ts +11 -11
- package/dist/engine/execution-env/node-execution-env.d.ts +3 -2
- package/dist/engine/execution-env/node-execution-env.js +4 -2
- package/dist/engine/harness/agent-harness.d.ts +47 -4
- package/dist/engine/harness/agent-harness.js +52 -9
- package/dist/engine/llm/types.d.ts +1 -1
- package/dist/engine/loop/agent-loop.js +15 -8
- package/dist/engine/loop/types.d.ts +5 -0
- package/dist/index.d.ts +21 -16
- package/dist/index.js +17 -12
- package/dist/internal/harness.d.ts +1 -0
- package/dist/orchestration/run-spec.d.ts +3 -0
- package/dist/orchestration/run-spec.js +3 -3
- package/dist/orchestration/run-workflow-tool.d.ts +12 -6
- package/dist/orchestration/run-workflow-tool.js +23 -5
- package/dist/orchestration/workflow-primitives.d.ts +4 -2
- package/dist/orchestration/workflow-primitives.js +1 -4
- package/dist/orchestration/workflow-types.d.ts +34 -4
- package/dist/orchestration/workflow-types.js +2 -0
- package/dist/orchestration/workflow.d.ts +39 -7
- package/dist/orchestration/workflow.js +234 -113
- package/dist/prompt-assembly/assemble.d.ts +2 -2
- package/dist/prompt-assembly/assemble.js +9 -9
- package/dist/prompt-assembly/composer.d.ts +6 -7
- package/dist/prompt-assembly/composer.js +10 -10
- package/dist/prompt-assembly/epoch.js +1 -1
- package/dist/prompt-assembly/event-registry.d.ts +0 -21
- package/dist/prompt-assembly/packs/sema-default.js +30 -30
- package/dist/prompt-assembly/types.d.ts +14 -14
- package/dist/prompts/coordinator.d.ts +0 -13
- package/dist/prompts/coordinator.js +0 -3
- package/dist/stores/file/checkpoint-store.js +1 -5
- package/dist/stores/file/file-history-store.js +2 -2
- package/dist/stores/file/permission-rule-store.d.ts +4 -35
- package/dist/stores/file/permission-rule-store.js +4 -148
- package/dist/tools/artifact/artifact-text.d.ts +62 -0
- package/dist/tools/artifact/artifact-text.js +68 -0
- package/dist/tools/artifact/artifact-tool.d.ts +52 -0
- package/dist/tools/artifact/artifact-tool.js +351 -0
- package/dist/tools/artifact/local-stub.d.ts +26 -0
- package/dist/tools/artifact/local-stub.js +296 -0
- package/dist/tools/fs/bash-readonly-classifier.d.ts +114 -48
- package/dist/tools/fs/bash-readonly-classifier.js +171 -60
- package/dist/tools/fs/encoding.d.ts +31 -40
- package/dist/tools/fs/fs-bash.d.ts +49 -45
- package/dist/tools/fs/fs-bash.js +19 -27
- package/dist/tools/fs/fs-pdf.d.ts +8 -8
- package/dist/tools/fs/fs-search-tools.d.ts +4 -4
- package/dist/tools/fs/fs-search-tools.js +5 -2
- package/dist/tools/fs/fs-shared.d.ts +146 -158
- package/dist/tools/fs/fs-shared.js +3 -11
- package/dist/tools/fs/fs-write.d.ts +8 -8
- package/dist/tools/fs/gh-rate-limit.d.ts +13 -14
- package/dist/tools/fs/index.d.ts +78 -78
- package/dist/tools/fs/index.js +4 -1
- package/dist/tools/fs/notebook.d.ts +24 -25
- package/dist/tools/fs/pdf.d.ts +3 -3
- package/dist/tools/fs/read-deny.d.ts +22 -22
- package/dist/tools/fs/read-face.d.ts +21 -24
- package/dist/tools/fs/repo-map.d.ts +8 -8
- package/dist/tools/fs/safety.d.ts +233 -130
- package/dist/tools/fs/safety.js +131 -31
- package/dist/tools/fs/search.d.ts +63 -71
- package/dist/tools/fs/search.js +8 -6
- package/package.json +6 -2
- package/test/export-surface.snapshot.json +119 -39
package/dist/tools/fs/safety.js
CHANGED
|
@@ -23,15 +23,6 @@ const SEP = "/";
|
|
|
23
23
|
export function isAbsolutePathForm(p) {
|
|
24
24
|
return p.startsWith("/") || /^[A-Za-z]:[\\/]/.test(p) || p.startsWith("\\\\");
|
|
25
25
|
}
|
|
26
|
-
function normalizeForCompare(p) {
|
|
27
|
-
let n = stripWin32ExtendedPrefix(p);
|
|
28
|
-
if (isUncPath(n) && isWinFormPath(n) && !isWin32DeviceNamespacePath(n))
|
|
29
|
-
n = foldUncLexically(n).key;
|
|
30
|
-
n = isWinFormPath(n) ? n.replace(/\\/g, "/") : n;
|
|
31
|
-
if (/^[A-Za-z]:(\/|$)/.test(n))
|
|
32
|
-
n = n[0].toLowerCase() + n.slice(1);
|
|
33
|
-
return n;
|
|
34
|
-
}
|
|
35
26
|
function nativeSepOf(p) {
|
|
36
27
|
return isWinFormPath(p) && p.includes("\\") ? "\\" : "/";
|
|
37
28
|
}
|
|
@@ -323,26 +314,137 @@ function foldUncLexically(p) {
|
|
|
323
314
|
const anchor = `\\\\${host}${share !== undefined ? `\\${share}` : ""}`;
|
|
324
315
|
return { key: out.length > 0 ? `${anchor}\\${out.join("\\")}` : anchor, climbedAboveShare: climbed };
|
|
325
316
|
}
|
|
317
|
+
export function pathFamilyOf(bases) {
|
|
318
|
+
const tree = bases.root ?? bases.cwd;
|
|
319
|
+
return tree === undefined ? undefined : isWinFormPath(tree) ? "win" : "posix";
|
|
320
|
+
}
|
|
321
|
+
function isUncSpelling(p) {
|
|
322
|
+
return /^[\\/]{2}[^\\/]+[\\/]+[^\\/]/.test(p);
|
|
323
|
+
}
|
|
324
|
+
export function isAbsoluteForFamily(family, p) {
|
|
325
|
+
return family === "win" ? /^[A-Za-z]:[\\/]/.test(p) || isUncSpelling(p) : p.startsWith("/");
|
|
326
|
+
}
|
|
327
|
+
export const WIN32_NAMESPACE_REFUSALS = ["device_namespace", "unmappable_namespace", "unc_on_posix_tree", "drive_relative"];
|
|
328
|
+
export const WIN32_NAMESPACE_REFUSAL_TEXT = {
|
|
329
|
+
device_namespace: "is a Win32 device-namespace path (\\\\.\\ names a raw device object, not a file)",
|
|
330
|
+
unmappable_namespace: "is a Win32 extended-length namespace path with no DOS-path equivalent (only \\\\?\\<drive>: and \\\\?\\UNC\\ forms name files)",
|
|
331
|
+
unc_on_posix_tree: "is a UNC (\\\\host\\share) spelling, which names no file on a POSIX tree",
|
|
332
|
+
drive_relative: "is drive-relative (a drive letter with no separator after the colon), a location no tree can locate",
|
|
333
|
+
};
|
|
334
|
+
export function win32NamespaceScreen(spelled, family) {
|
|
335
|
+
if (family === "win" && /^[\\/]{2}[?.][\\/]/.test(spelled) && !/^\\\\[?.]\\/.test(spelled))
|
|
336
|
+
spelled = spelled.replace(/\//g, "\\");
|
|
337
|
+
if (isWin32DeviceNamespacePath(spelled))
|
|
338
|
+
return { ok: false, refuse: "device_namespace" };
|
|
339
|
+
const mapped = stripWin32ExtendedPrefix(spelled);
|
|
340
|
+
if (mapped.startsWith("\\\\?\\"))
|
|
341
|
+
return { ok: false, refuse: "unmappable_namespace" };
|
|
342
|
+
if (mapped.startsWith("\\\\") && family === "posix")
|
|
343
|
+
return { ok: false, refuse: "unc_on_posix_tree" };
|
|
344
|
+
if (family === "win" && /^[A-Za-z]:(?![\\/])/.test(mapped))
|
|
345
|
+
return { ok: false, refuse: "drive_relative" };
|
|
346
|
+
return { ok: true, spelled: mapped };
|
|
347
|
+
}
|
|
348
|
+
export function pathIdentityOf(spelled, family, anchor) {
|
|
349
|
+
if (typeof spelled !== "string")
|
|
350
|
+
return { ok: false, reason: "relative" };
|
|
351
|
+
if (family !== "win") {
|
|
352
|
+
if (!spelled.startsWith("/"))
|
|
353
|
+
return { ok: false, reason: "relative" };
|
|
354
|
+
return { ok: true, identity: mintIdentity("", spelled.split("/"), false) };
|
|
355
|
+
}
|
|
356
|
+
const screened = win32NamespaceScreen(spelled, "win");
|
|
357
|
+
if (!screened.ok)
|
|
358
|
+
return { ok: false, reason: screened.refuse };
|
|
359
|
+
let s = screened.spelled;
|
|
360
|
+
if (s.startsWith("/") && isAbsoluteForFamily("win", s.slice(1)))
|
|
361
|
+
s = s.slice(1);
|
|
362
|
+
if (isUncSpelling(s)) {
|
|
363
|
+
const segs = s.slice(2).split(/[\\/]+/).filter((x) => x !== "");
|
|
364
|
+
const host = segs.shift();
|
|
365
|
+
const share = segs.shift();
|
|
366
|
+
return { ok: true, identity: mintIdentity(`//${host}/${share}`, segs, true) };
|
|
367
|
+
}
|
|
368
|
+
const drive = /^([A-Za-z]):[\\/]/.exec(s) ?? /^[\\/]([A-Za-z])(?=[\\/]|$)/.exec(s);
|
|
369
|
+
if (drive !== null)
|
|
370
|
+
return { ok: true, identity: mintIdentity(`/${drive[1].toLowerCase()}`, s.slice(2).split(/[\\/]/), false) };
|
|
371
|
+
if (s.startsWith("/") || s.startsWith("\\")) {
|
|
372
|
+
const root = anchor === undefined ? undefined : pathIdentityOf(anchor, "win");
|
|
373
|
+
if (root === undefined || !root.ok)
|
|
374
|
+
return { ok: false, reason: "unanchored" };
|
|
375
|
+
return { ok: true, identity: mintIdentity(identityAnchorOf(root.identity), s.slice(1).split(/[\\/]/), false) };
|
|
376
|
+
}
|
|
377
|
+
return { ok: false, reason: "relative" };
|
|
378
|
+
}
|
|
379
|
+
function mintIdentity(anchor, segments, stripTail) {
|
|
380
|
+
const out = [];
|
|
381
|
+
for (const seg of segments) {
|
|
382
|
+
if (seg === "" || seg === ".")
|
|
383
|
+
continue;
|
|
384
|
+
if (seg === "..") {
|
|
385
|
+
if (out.length > 0)
|
|
386
|
+
out.pop();
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
const stripped = stripTail ? seg.replace(/[. ]+$/, "") : seg;
|
|
390
|
+
out.push(stripped.length > 0 ? stripped : seg);
|
|
391
|
+
}
|
|
392
|
+
return (out.length === 0 ? anchor || "/" : `${anchor}/${out.join("/")}`);
|
|
393
|
+
}
|
|
394
|
+
function identityAnchorOf(identity) {
|
|
395
|
+
return identity.startsWith("//") ? identity.split("/").slice(0, 4).join("/") : identity.slice(0, 2);
|
|
396
|
+
}
|
|
397
|
+
export function isUncIdentity(identity) {
|
|
398
|
+
return identity.startsWith("//");
|
|
399
|
+
}
|
|
400
|
+
export function isPathIdentity(s, family) {
|
|
401
|
+
const r = pathIdentityOf(s, family);
|
|
402
|
+
return r.ok && r.identity === s;
|
|
403
|
+
}
|
|
404
|
+
export function isIdentityDir(identity, family) {
|
|
405
|
+
if (family !== "win")
|
|
406
|
+
return identity !== "/";
|
|
407
|
+
return identity.length > identityAnchorOf(identity).length;
|
|
408
|
+
}
|
|
409
|
+
export function carriesDotSegment(spelled, family) {
|
|
410
|
+
return (family === "win" ? /(^|[\\/])\.{1,2}([\\/]|$)/ : /(^|\/)\.{1,2}(\/|$)/).test(spelled);
|
|
411
|
+
}
|
|
412
|
+
export function clipSpelling(spelled) {
|
|
413
|
+
return spelled.length > 512 ? `${spelled.slice(0, 512)}…` : spelled;
|
|
414
|
+
}
|
|
415
|
+
export function isShellRootedSpellingUnmapped(family, spelled) {
|
|
416
|
+
return family === "win" && (spelled.startsWith("/") || spelled.startsWith("\\"));
|
|
417
|
+
}
|
|
418
|
+
export function nativeUncSpellingOf(family, spelled) {
|
|
419
|
+
return family === "win" && isUncSpelling(spelled) ? `\\\\${spelled.slice(2).replace(/\//g, "\\")}` : spelled;
|
|
420
|
+
}
|
|
421
|
+
export function joinForFamily(family, base, rel) {
|
|
422
|
+
if (family === "posix")
|
|
423
|
+
return `${base.replace(/\/+$/, "")}/${rel}`;
|
|
424
|
+
if (rel.startsWith("/") || rel.startsWith("\\")) {
|
|
425
|
+
const root = /^[A-Za-z]:/.exec(base)?.[0] ?? /^[\\/]{2}[^\\/]+[\\/]+[^\\/]+/.exec(base)?.[0];
|
|
426
|
+
return root === undefined ? rel : root + rel;
|
|
427
|
+
}
|
|
428
|
+
return `${base.replace(/[\\/]+$/, "")}${base.includes("\\") ? "\\" : "/"}${rel}`;
|
|
429
|
+
}
|
|
326
430
|
export function lexicalViewOf(spelled, base) {
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
: `${base.replace(/[\\/]+$/, "")}${isWinFormPath(base) && base.includes("\\") ? "\\" : "/"}${spelled}`;
|
|
330
|
-
return normalizeAbsPathLexically(abs);
|
|
431
|
+
const family = pathFamilyOf({ root: base });
|
|
432
|
+
return normalizeAbsPathLexically(isAbsoluteForFamily(family, spelled) ? spelled : joinForFamily(family, base, spelled));
|
|
331
433
|
}
|
|
332
434
|
function within(rootCanonical, p) {
|
|
333
435
|
if (!rootCanonical)
|
|
334
436
|
return false;
|
|
335
|
-
const
|
|
336
|
-
const
|
|
337
|
-
|
|
437
|
+
const family = pathFamilyOf({ root: rootCanonical });
|
|
438
|
+
const rootN = pathIdentityOf(rootCanonical, family);
|
|
439
|
+
const pN = pathIdentityOf(p, family);
|
|
440
|
+
if (!rootN.ok || !pN.ok)
|
|
338
441
|
return false;
|
|
339
|
-
if (pN === rootN)
|
|
442
|
+
if (pN.identity === rootN.identity)
|
|
340
443
|
return true;
|
|
341
|
-
|
|
342
|
-
return pN.startsWith(base);
|
|
444
|
+
return pN.identity.startsWith(rootN.identity.endsWith(SEP) ? rootN.identity : rootN.identity + SEP);
|
|
343
445
|
}
|
|
344
446
|
export async function resolveKey(env, rootCanonical, path, signal, baseCwd, additionalRootsCanonical, exactFileReadExemption, readDeny, readFace) {
|
|
345
|
-
const canon = await canonicalizeTarget(env, path, signal, baseCwd);
|
|
447
|
+
const canon = await canonicalizeTarget(env, path, signal, pathFamilyOf({ root: rootCanonical }), baseCwd);
|
|
346
448
|
if (!canon.ok)
|
|
347
449
|
return { ok: false, violation: { code: "invalid", message: canon.message } };
|
|
348
450
|
const key = canon.key;
|
|
@@ -397,23 +499,21 @@ export async function resolveKey(env, rootCanonical, path, signal, baseCwd, addi
|
|
|
397
499
|
}
|
|
398
500
|
return { ok: true, key };
|
|
399
501
|
}
|
|
400
|
-
export async function canonicalizeTarget(env, path, signal, baseCwd) {
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
const unprefixed = stripWin32ExtendedPrefix(path);
|
|
405
|
-
if (unprefixed.startsWith("\\\\?\\")) {
|
|
406
|
-
return { ok: false, message: `path "${path}" is a Win32 extended-length namespace path with no DOS-path equivalent (only \\\\?\\<drive>: and \\\\?\\UNC\\ forms name files); refused.` };
|
|
502
|
+
export async function canonicalizeTarget(env, path, signal, family, baseCwd) {
|
|
503
|
+
const screened = win32NamespaceScreen(path, family);
|
|
504
|
+
if (!screened.ok) {
|
|
505
|
+
return { ok: false, message: `path "${clipSpelling(path)}" ${WIN32_NAMESPACE_REFUSAL_TEXT[screened.refuse]}; refused (${screened.refuse}).`, refused: screened.refuse };
|
|
407
506
|
}
|
|
408
|
-
|
|
507
|
+
const unprefixed = screened.spelled;
|
|
508
|
+
if (family === "win" && isUncSpelling(unprefixed)) {
|
|
409
509
|
const folded = foldUncLexically(unprefixed);
|
|
410
510
|
if (folded.climbedAboveShare) {
|
|
411
511
|
return { ok: false, message: `path "${path}" climbs above its UNC share root via ".." (\\\\host\\share is the root of a network path); refused.` };
|
|
412
512
|
}
|
|
413
513
|
return { ok: true, key: folded.key };
|
|
414
514
|
}
|
|
415
|
-
const spelled = unprefixed.startsWith("//") ? unprefixed.replace(/^\/+/, "/") : unprefixed;
|
|
416
|
-
const target = baseCwd && !
|
|
515
|
+
const spelled = family === "posix" && unprefixed.startsWith("//") ? unprefixed.replace(/^\/+/, "/") : unprefixed;
|
|
516
|
+
const target = baseCwd && !isAbsoluteForFamily(family, spelled) ? joinForFamily(family, baseCwd, spelled) : spelled;
|
|
417
517
|
const absR = await env.absolutePath(target, signal);
|
|
418
518
|
if (!absR.ok)
|
|
419
519
|
return { ok: false, message: `cannot resolve path "${path}": ${absR.error.message}` };
|
|
@@ -451,7 +551,7 @@ export async function canonicalizeTarget(env, path, signal, baseCwd) {
|
|
|
451
551
|
if (!link.ok) {
|
|
452
552
|
return { ok: false, message: `cannot read symlink "${path}": ${link.error.message}`, unresolvedSymlink: true };
|
|
453
553
|
}
|
|
454
|
-
const linkTarget =
|
|
554
|
+
const linkTarget = isAbsoluteForFamily(family, link.value) ? link.value : joinForFamily(family, parentDir(abs), link.value);
|
|
455
555
|
const reabs = await env.absolutePath(linkTarget, signal);
|
|
456
556
|
if (!reabs.ok) {
|
|
457
557
|
return { ok: false, message: `cannot resolve symlink target "${linkTarget}" for "${path}": ${reabs.error.message}`, unresolvedSymlink: true };
|
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import type { ExecutionEnv } from "../../internal/harness-types.js";
|
|
2
|
-
/**
|
|
2
|
+
/** The JS scanner's own output ceiling, in bytes of emitted content rows. Pinned to the
|
|
3
3
|
* ripgrep leg's figure ({@link MAX_EXEC_OUTPUT_BYTES}, the exec pipe's rolling-tail bound) so the two
|
|
4
4
|
* legs cannot disagree about how much one search may return; see the collection site in {@link jsGrep}
|
|
5
5
|
* for why this leg bounds the HEAD where the pipe bounds the tail. */
|
|
6
6
|
export declare const JS_GREP_OUTPUT_MAX_BYTES: number;
|
|
7
|
-
/** Directories never worth crawling
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* identical either way). See {@link buildIgnore}'s own comment for why this list is now the ENTIRE
|
|
12
|
-
* directory-skip story — the blanket "any dot-prefixed name is invisible" rule this list used to sit
|
|
13
|
-
* beside is gone. */
|
|
7
|
+
/** Directories never worth crawling — dependency/build/cache trees, PLUS the six VCS metadata
|
|
8
|
+
* directories CC names explicitly (git/svn/mercurial/bazaar/jujutsu/sapling). See {@link buildIgnore}'s
|
|
9
|
+
* own comment for why this list is the ENTIRE directory-skip story: there is deliberately no blanket
|
|
10
|
+
* "any dot-prefixed name is invisible" rule beside it. */
|
|
14
11
|
export declare const DEFAULT_IGNORE_DIRS: ReadonlySet<string>;
|
|
15
12
|
export interface GrepParams {
|
|
16
13
|
/** Regular expression (ripgrep semantics) — CC 2.1.209 parity: the pattern is ALWAYS a regex; the
|
|
@@ -19,12 +16,12 @@ export interface GrepParams {
|
|
|
19
16
|
pattern: string;
|
|
20
17
|
/** Restrict the search to this sub-path (relative to root, or absolute within root). */
|
|
21
18
|
path?: string;
|
|
22
|
-
/** Only search files matching this glob (e.g. `*.ts`, `*.{ts,tsx}`).
|
|
19
|
+
/** Only search files matching this glob (e.g. `*.ts`, `*.{ts,tsx}`). CC parity: a
|
|
23
20
|
* comma/whitespace-separated list (`"*.ts,*.tsx"`) is split into multiple globs (brace globs kept whole). */
|
|
24
21
|
glob?: string;
|
|
25
|
-
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
22
|
+
/** CC `-o`: print only the matched (non-empty) parts of each matching line, one match per output
|
|
23
|
+
* line (rg -o / --only-matching). content mode only; ignored otherwise (CC parity). With
|
|
24
|
+
* `multiline` the JS fallback emits the matched SEGMENT's lines (rg -U -o shape). */
|
|
28
25
|
only_matching?: boolean;
|
|
29
26
|
/** content: `path:line:text`. files_with_matches (default, CC 2.1.209): `path`. count: `path:count`. */
|
|
30
27
|
output_mode?: "content" | "files_with_matches" | "count";
|
|
@@ -46,7 +43,7 @@ export interface GrepParams {
|
|
|
46
43
|
/** Skip the first N output lines — CC's `offset` (pagination partner of head_limit). */
|
|
47
44
|
offset?: number;
|
|
48
45
|
}
|
|
49
|
-
/**
|
|
46
|
+
/** CC 2.1.198 Grep parity: split the `glob` param into individual glob
|
|
50
47
|
* tokens — whitespace-separated first; a token WITHOUT braces is further split on commas
|
|
51
48
|
* (`"*.ts,*.tsx"` → two globs), while a brace token (`*.{ts,tsx}`) is kept whole (its commas are
|
|
52
49
|
* alternation syntax, not separators). CC-verbatim splitting rules. */
|
|
@@ -54,28 +51,26 @@ export declare function splitGlobParam(glob: string): string[];
|
|
|
54
51
|
/**
|
|
55
52
|
* POSIX-safe single-quote so an arbitrary model-supplied string can't break out of the shell command that
|
|
56
53
|
* runs ripgrep. Wraps in single quotes (inside which the shell expands nothing — no `$()`, backtick, `;`, or
|
|
57
|
-
* glob) and escapes any embedded single quote via the `'\''` idiom (close, escaped-quote, reopen).
|
|
58
|
-
* for direct injection testing
|
|
54
|
+
* glob) and escapes any embedded single quote via the `'\''` idiom (close, escaped-quote, reopen).
|
|
55
|
+
* Exported for direct injection testing.
|
|
59
56
|
*/
|
|
60
57
|
export declare function shellQuote(s: string): string;
|
|
61
58
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* literal match.
|
|
59
|
+
* The tokens of a `glob` argument that this engine CANNOT compile as glob syntax, i.e. the ones whose
|
|
60
|
+
* metacharacter would silently degrade to a literal match.
|
|
65
61
|
*
|
|
66
62
|
* The harm this closes is specific and asymmetric. An unknown `type` degrades to searching EVERY file —
|
|
67
63
|
* a SUPERSET of what was asked, wrong but self-evidently over-broad, and sema already discloses it with
|
|
68
64
|
* a `[note: unknown type …]` line. An unparseable `glob` degrades the other way: `src/[ab.ts` stops
|
|
69
65
|
* being a character class and becomes a request for a file literally NAMED `src/[ab.ts`, so the filter
|
|
70
|
-
* silently narrows to (almost always) nothing and the search answers a clean "No matches." — a
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* answer.
|
|
66
|
+
* silently narrows to (almost always) nothing and the search answers a clean "No matches." — a false
|
|
67
|
+
* negative wearing the exact shape of a successful exhaustive search. This repo's standing verdict on
|
|
68
|
+
* that class is: no real data ⇒ isError, never a degraded answer.
|
|
74
69
|
*
|
|
75
70
|
* CC hard-errors on the same input class rather than searching at all — its ripgrep stderr matcher
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
71
|
+
* covers `error parsing glob` (alongside regex-parse / unrecognized-file-type / flag-parse errors) and
|
|
72
|
+
* raises `Search failed — ripgrep rejected the pattern, glob, or file type without searching`. sema
|
|
73
|
+
* rejects at the INPUT level instead of waiting for a stderr string, because sema has
|
|
79
74
|
* two search engines (rg + the JS fallback) and only the rg one would ever produce that stderr: a
|
|
80
75
|
* rejection that fires only when ripgrep happens to be installed is not a contract. The `type` leg
|
|
81
76
|
* deliberately does NOT get this treatment (see the `typeNote` site) — over-broad-with-a-note is an
|
|
@@ -85,20 +80,18 @@ export declare function invalidGlobTokens(glob: string): string[];
|
|
|
85
80
|
/** Lightweight `.gitignore` matcher (handles comments, negation, dir-only `/`, anchoring, `*`/`?`). */
|
|
86
81
|
export declare function gitignoreMatcher(content: string): (rel: string, isDir: boolean) => boolean;
|
|
87
82
|
/** Build the combined ignore predicate (ignore-set ∪ .gitignore) for a JS-fallback walk.
|
|
88
|
-
* Exported so repo-map
|
|
83
|
+
* Exported so repo-map walks with the SAME ignore semantics as grep/glob.
|
|
89
84
|
*
|
|
90
|
-
*
|
|
91
|
-
* "!"+N)`, @367957/367959-367965 Glob `CLAUDE_CODE_GLOB_HIDDEN` defaults to true and also passes `--hidden`): CC's own
|
|
92
|
-
* "keep it quiet" posture is NOT "every dot-prefixed name is invisible" — it's `--hidden` (show
|
|
85
|
+
* CC's "keep it quiet" posture is NOT "every dot-prefixed name is invisible" — it is `--hidden` (show
|
|
93
86
|
* everything dot-prefixed) plus an explicit VCS-directory exclusion list ({@link DEFAULT_IGNORE_DIRS}).
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
87
|
+
* A blanket `base.startsWith(".")` rule here would make EVERY hidden file/dir invisible with no caveat
|
|
88
|
+
* — `.github/`, `.claude/`, `.env.example` would not exist as far as Grep/Glob/repo-map are concerned,
|
|
89
|
+
* not even as an honest "search skipped this" note, and an unflagged gap reads as exhaustive. So
|
|
90
|
+
* {@link DEFAULT_IGNORE_DIRS} (dependency/build/cache trees + the 6 VCS metadata dirs) and `.gitignore`
|
|
91
|
+
* are the entire directory-skip story, matching CC. */
|
|
99
92
|
export declare function buildIgnore(env: ExecutionEnv, root: string, signal?: AbortSignal): Promise<(rel: string, isDir: boolean) => boolean>;
|
|
100
|
-
/** What a bounded walk actually saw — so consumers can be HONEST about incompleteness
|
|
101
|
-
*
|
|
93
|
+
/** What a bounded walk actually saw — so consumers can be HONEST about incompleteness: silently
|
|
94
|
+
* bounded results must never read as exhaustive. */
|
|
102
95
|
export interface WalkResult {
|
|
103
96
|
/** Greppable text files (binary-extension and oversize files excluded). */
|
|
104
97
|
files: string[];
|
|
@@ -113,35 +106,34 @@ export interface WalkResult {
|
|
|
113
106
|
/** Non-binary files skipped by content search for being over {@link FILE_MAX_BYTES} (also in
|
|
114
107
|
* `nameOnly`). Surfaced by grep so "No matches." can't silently mean "didn't look". */
|
|
115
108
|
skippedLarge: number;
|
|
116
|
-
/**
|
|
117
|
-
* walk never saw. */
|
|
109
|
+
/** Directories whose listing FAILED (EACCES, transient I/O) — whole subtrees the walk never saw. */
|
|
118
110
|
skippedUnreadable: number;
|
|
119
|
-
/**
|
|
111
|
+
/** The walk was cut short by an abort — the result is a prefix, not a survey. */
|
|
120
112
|
aborted: boolean;
|
|
121
|
-
/** CC 2.1.209 Glob parity
|
|
113
|
+
/** CC 2.1.209 Glob parity: modification time (ms epoch) per walked path (files AND
|
|
122
114
|
* name-only entries), captured from the listing that discovered them — so glob can sort by mtime
|
|
123
115
|
* without a second stat pass. An env whose listings carry no numeric `mtimeMs` simply leaves
|
|
124
116
|
* entries out of this map (consumers fall back to name order). */
|
|
125
117
|
mtimes: Map<string, number>;
|
|
126
|
-
/**
|
|
118
|
+
/** Entries withheld by the sensitive-path read deny list: PRUNE-ROOT count only
|
|
127
119
|
* (a denied directory's descendants are not enumerated, so they cannot be counted — the note says
|
|
128
120
|
* so). 0 when no deny judge was supplied or nothing matched. */
|
|
129
121
|
denyPruned: number;
|
|
130
122
|
/** The deny patterns that pruned something, first-seen order (for the disclosure note). */
|
|
131
123
|
denyPatterns: string[];
|
|
132
124
|
}
|
|
133
|
-
/**
|
|
125
|
+
/** Any-cause incompleteness — a result built on this walk must not present itself as exhaustive. */
|
|
134
126
|
export declare function walkIsPartial(w: WalkResult): boolean;
|
|
135
127
|
/** Bounded, ignore-aware recursive walk. Returns absolute file paths under `start`. Exported for
|
|
136
|
-
* repo-map
|
|
128
|
+
* repo-map — same bounded/ignore-aware traversal as grep/glob, one source of truth. */
|
|
137
129
|
export declare function walk(env: ExecutionEnv, root: string, start: string, ignore: (rel: string, isDir: boolean) => boolean, signal?: AbortSignal, deny?: {
|
|
138
130
|
matchPath(path: string): {
|
|
139
131
|
pattern: string;
|
|
140
132
|
} | null;
|
|
141
133
|
}): Promise<WalkResult>;
|
|
142
|
-
/**
|
|
143
|
-
*
|
|
144
|
-
*
|
|
134
|
+
/** The JS-leg withholding disclosure: an empty result must stay distinguishable from
|
|
135
|
+
* searched-and-empty, so this note rides EVERY output shape when something was pruned. Distinct from
|
|
136
|
+
* {@link walkCaveat}: pruning is deliberate withholding, not incompleteness.
|
|
145
137
|
* The `[note:` prefix keeps it out of the tool layer's structured row extraction. */
|
|
146
138
|
export declare function denyWithheldNote(w: Pick<WalkResult, "denyPruned" | "denyPatterns">): string;
|
|
147
139
|
/** Injectable execution boundaries for the JS-fallback scan (tests shrink them; callers use defaults). */
|
|
@@ -150,13 +142,13 @@ export interface JsGrepGuards {
|
|
|
150
142
|
budgetMs?: number;
|
|
151
143
|
/** Longest line/content (chars) a gray-zone pattern may be matched against. */
|
|
152
144
|
longLineLimit?: number;
|
|
153
|
-
/**
|
|
145
|
+
/** Bytes of content rows this scan may accumulate before it stops collecting and
|
|
154
146
|
* says so (default {@link JS_GREP_OUTPUT_MAX_BYTES}). A seam for the same reason the two above
|
|
155
147
|
* are: the boundary is pinnable at a testable size instead of only at an 8MB harness. */
|
|
156
148
|
outputMaxBytes?: number;
|
|
157
149
|
}
|
|
158
|
-
/**
|
|
159
|
-
*
|
|
150
|
+
/** What a traversal withheld under the sensitive-path read deny list, structured (the prose note is
|
|
151
|
+
* the model-facing twin). Three shapes: `pruned_count` = the JS walker
|
|
160
152
|
* counted its prune roots; `existence` = the rg leg's bounded probe CONFIRMED matching entries exist
|
|
161
153
|
* under the scope (rg's own exclusions are silent, so the count is unknowable); `probe_failed` =
|
|
162
154
|
* the probe itself failed — entries MAY have been withheld. */
|
|
@@ -180,11 +172,11 @@ export interface ReadDenyJudge {
|
|
|
180
172
|
}>;
|
|
181
173
|
}
|
|
182
174
|
/** JS-fallback grep: ignore-aware walk + per-line scan, honoring output_mode / context / head_limit.
|
|
183
|
-
*
|
|
175
|
+
* `deny` prunes the walk (note appended to every output shape); `denyOut` is the
|
|
184
176
|
* structured twin — a sink because this function's dozen error returns predate the facts (the sink
|
|
185
177
|
* is written the moment the walk lands, whatever the scan then returns).
|
|
186
178
|
*
|
|
187
|
-
*
|
|
179
|
+
* The SERVED rows ride back next to the text. This scanner knows every result's
|
|
188
180
|
* identity natively (the walk hands it whole paths), so a row's `path` is that walked identity,
|
|
189
181
|
* never a cut of the rendered line — the mis-split family the text re-parse carries cannot exist
|
|
190
182
|
* here. The rows ARE the window `text` shows: post offset, post row cap, post byte ceiling, in the
|
|
@@ -222,7 +214,7 @@ export type GrepDegradation = {
|
|
|
222
214
|
};
|
|
223
215
|
/** Structured grep result: the model-facing text plus the degradation facts, so the tool layer can
|
|
224
216
|
* ship them on the structured frame instead of leaving them prose-only. */
|
|
225
|
-
/**
|
|
217
|
+
/** One SERVED result RECORD with its path taken from the engine's own knowledge, exactly the
|
|
226
218
|
* window `text` shows (post cap/offset): ripgrep's `--null` path field ({@link parseRgRecords}) on
|
|
227
219
|
* the rg legs, the walked file identity ({@link jsGrepDetailed}) on the JS-scanner legs. A record is
|
|
228
220
|
* one rendered line except where a path carries a newline (see {@link formatRgRecords}), which is
|
|
@@ -247,50 +239,50 @@ export interface GrepRow {
|
|
|
247
239
|
}
|
|
248
240
|
export interface GrepRunResult {
|
|
249
241
|
text: string;
|
|
250
|
-
/** Served rows
|
|
251
|
-
*
|
|
242
|
+
/** Served rows: present on BOTH engine legs — ripgrep's and the JS scanner's, including the
|
|
243
|
+
* re-scan legs. Absent only when the JS scanner refused the search outright (a typed
|
|
252
244
|
* `Error (grep): …` text): a refusal has no row set, truthfully, rather than an empty one. */
|
|
253
245
|
rows?: readonly GrepRow[];
|
|
254
246
|
degraded?: GrepDegradation;
|
|
255
|
-
/**
|
|
247
|
+
/** Deny-list withholding facts (see {@link ReadDenyWithheld}); absent = nothing
|
|
256
248
|
* withheld / no deny judge in play. */
|
|
257
249
|
withheld?: ReadDenyWithheld;
|
|
258
250
|
}
|
|
259
251
|
/** The output modes the rg leg and {@link jsGrep} share. */
|
|
260
252
|
type GrepOutputMode = "content" | "files_with_matches" | "count";
|
|
261
253
|
/**
|
|
262
|
-
*
|
|
254
|
+
* The rg leg's deny TRIPWIRE. rg's exclusion globs are spelled
|
|
263
255
|
* from the pattern text and cannot express the win32 component-alias family (`.aws.` / `.aws ` —
|
|
264
256
|
* trailing dots/spaces the win32 namespace strips), so a directory planted under an alias spelling
|
|
265
257
|
* slips past the globs while the shared `matchPath` (two-view, read-deny.ts) correctly names it —
|
|
266
258
|
* the JS leg pruned it, the rg leg returned its contents, and the same tree answered differently
|
|
267
259
|
* depending on whether ripgrep was installed.
|
|
268
260
|
*
|
|
269
|
-
*
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
261
|
+
* FILTERING rg's text output line-by-line is not an option, on two counts: ripgrep prints filenames
|
|
262
|
+
* verbatim, so a path component containing a NEWLINE splits one record across physical lines whose
|
|
263
|
+
* fragments carry no `:line:` boundary — the filter keeps them and the deny-listed content passes (a
|
|
264
|
+
* protection hole, not a precision residual); and pruning every line of a partial run fabricates a
|
|
265
|
+
* `No matches.` row inside the structured card. So this function edits nothing: it JUDGES. Any deny
|
|
266
|
+
* hit — or any record the format cannot account for —
|
|
275
267
|
* trips, and the caller abandons the rg run for the JS scanner, whose walk prunes with the
|
|
276
268
|
* authoritative judge and needs no path parsing at all. rg stays the fast path for the common case
|
|
277
269
|
* (no guarded entries in the result); the moment a guarded spelling is involved, the engine that
|
|
278
270
|
* cannot mis-parse it owns the answer.
|
|
279
271
|
*
|
|
280
|
-
*
|
|
272
|
+
* What it judges is ripgrep's OWN path field ({@link parseRgRecords}, `--null`),
|
|
281
273
|
* not a prefix cut at a guessed separator. Trip conditions, all three modes alike: a record whose
|
|
282
274
|
* path the deny judge names trips as `deny-hit`; a record with no path field at all (rg's binary-file
|
|
283
275
|
* notice, a no-filename row, an unterminated tail, output from an env that ignored `--null`) is
|
|
284
276
|
* unaccountable and trips as `ambiguous-record` — same abandonment, same reason code as before.
|
|
285
277
|
* `dropIncompleteTail` mirrors {@link parseRgRecords}: on the partial legs the cut tail is dropped
|
|
286
278
|
* BEFORE judging, because judging a record the caller will never receive can only cost an
|
|
287
|
-
* unnecessary
|
|
279
|
+
* unnecessary re-scan — every record that does reach the output is still judged, which is what the
|
|
288
280
|
* rule is for. (A killed stream ending mid-record is an EXPECTED shape with a rule of its own; the
|
|
289
281
|
* unaccountable arm is for records that survive that rule.)
|
|
290
282
|
* `--` group separators and empty lines carry no path by construction and pass. The `:digits:` /
|
|
291
|
-
* `-digits-` boundary family
|
|
292
|
-
*
|
|
293
|
-
*
|
|
283
|
+
* `-digits-` boundary family a guessing parser would have to reason about is gone: both of its
|
|
284
|
+
* residuals (over-tripping on a cluster inside match TEXT, and the dedup key folding two denied files
|
|
285
|
+
* whose own names carry a cluster) were artifacts of that decision, not of the judge.
|
|
294
286
|
*/
|
|
295
287
|
export declare function rgOutputDenyTripwire(stdout: string, mode: GrepOutputMode, judge: Pick<ReadDenyJudge, "matchPath">, opts?: {
|
|
296
288
|
dropIncompleteTail?: boolean;
|
|
@@ -303,21 +295,21 @@ export declare function rgOutputDenyTripwire(stdout: string, mode: GrepOutputMod
|
|
|
303
295
|
};
|
|
304
296
|
/** ripgrep grep: build flags from params, run, normalize to the same output as {@link jsGrep}.
|
|
305
297
|
* Result-fidelity contract: output ripgrep DID produce is never silently replaced by a fallback
|
|
306
|
-
*
|
|
298
|
+
* re-scan — partial results ship with a caveat; only a zero-output failure degrades to
|
|
307
299
|
* {@link jsGrep}, and that degradation is declared in both text and structure. An external abort
|
|
308
300
|
* propagates as the interruption it is (no fallback rerun). */
|
|
309
301
|
export declare function rgGrepDetailed(env: ExecutionEnv, root: string, p: GrepParams, signal?: AbortSignal, deny?: ReadDenyJudge): Promise<GrepRunResult>;
|
|
310
|
-
/** grep dispatch: ripgrep when the env has it, else the hardened JS fallback
|
|
302
|
+
/** grep dispatch: ripgrep when the env has it, else the hardened JS fallback.
|
|
311
303
|
* Returns the degradation facts alongside the text so the tool layer can surface them structurally. */
|
|
312
304
|
export declare function runGrepDetailed(env: ExecutionEnv, root: string, p: GrepParams, signal?: AbortSignal, deny?: ReadDenyJudge): Promise<GrepRunResult>;
|
|
313
305
|
/** Text-only wrapper of {@link runGrepDetailed}. */
|
|
314
306
|
export declare function runGrep(env: ExecutionEnv, root: string, p: GrepParams, signal?: AbortSignal): Promise<string>;
|
|
315
|
-
/** glob: find files by name pattern, with optional sub-path scoping; returns RELATIVE paths
|
|
307
|
+
/** glob: find files by name pattern, with optional sub-path scoping; returns RELATIVE paths. */
|
|
316
308
|
export declare function runGlob(env: ExecutionEnv, root: string, pattern: string, opts?: {
|
|
317
309
|
path?: string;
|
|
318
310
|
max?: number;
|
|
319
311
|
}, signal?: AbortSignal): Promise<string>;
|
|
320
|
-
/** Structured sibling of {@link runGlob}
|
|
312
|
+
/** Structured sibling of {@link runGlob}: same walk/match, plus the CC-shaped facts a
|
|
321
313
|
* shell renders (`filenames`/`numFiles`/`truncated`/`durationMs`/`totalMatches`/`countIsComplete`)
|
|
322
314
|
* without re-parsing the text.
|
|
323
315
|
*
|
package/dist/tools/fs/search.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { delimitUntrusted } from "../../core/untrusted-text.js";
|
|
2
2
|
import { MAX_EXEC_OUTPUT_BYTES } from "../../core/exec-output-tail.js";
|
|
3
|
-
import { hasBinaryExtension,
|
|
3
|
+
import { hasBinaryExtension, isAbsoluteForFamily, isWinFormPath, joinForFamily, pathFamilyOf } from "./safety.js";
|
|
4
4
|
const WALK_MAX_FILES = 5000;
|
|
5
5
|
const WALK_MAX_DEPTH = 32;
|
|
6
6
|
const GREP_DEFAULT_CAP = 250;
|
|
@@ -776,7 +776,8 @@ export async function jsGrepDetailed(env, root, p, signal, guards, deny, denyOut
|
|
|
776
776
|
}
|
|
777
777
|
const ignore = await buildIgnore(env, root, signal);
|
|
778
778
|
const rootPrefix = root.replace(/[\\/]+$/, "") + (root.includes("\\") ? "\\" : "/");
|
|
779
|
-
const
|
|
779
|
+
const family = pathFamilyOf({ root });
|
|
780
|
+
const start = p.path ? (isAbsoluteForFamily(family, p.path) ? p.path : joinForFamily(family, root, p.path)) : root;
|
|
780
781
|
let walked;
|
|
781
782
|
if (p.path) {
|
|
782
783
|
const info = await env.fileInfo(start, signal);
|
|
@@ -1054,10 +1055,10 @@ export function detectRipgrep(env) {
|
|
|
1054
1055
|
async function sortRgFilesByMtime(env, root, records, signal) {
|
|
1055
1056
|
if (records.length === 0)
|
|
1056
1057
|
return [...records];
|
|
1057
|
-
const
|
|
1058
|
+
const family = pathFamilyOf({ root });
|
|
1058
1059
|
const toAbs = (p) => {
|
|
1059
1060
|
const stripped = p.startsWith("./") ? p.slice(2) : p;
|
|
1060
|
-
return
|
|
1061
|
+
return isAbsoluteForFamily(family, stripped) ? stripped : joinForFamily(family, root, stripped);
|
|
1061
1062
|
};
|
|
1062
1063
|
const paths = records.map((r) => r.path ?? r.text);
|
|
1063
1064
|
const infos = await Promise.all(paths.map((p) => env.fileInfo(toAbs(p), signal)));
|
|
@@ -1231,7 +1232,7 @@ export async function rgGrepDetailed(env, root, p, signal, deny) {
|
|
|
1231
1232
|
if (deny === undefined || deny.rgExclusionGlobs.length === 0)
|
|
1232
1233
|
return { note: "" };
|
|
1233
1234
|
if (p.path) {
|
|
1234
|
-
const startPath =
|
|
1235
|
+
const startPath = isAbsoluteForFamily(pathFamilyOf({ root }), p.path) ? p.path : joinForFamily(pathFamilyOf({ root }), root, p.path);
|
|
1235
1236
|
const info = await env.fileInfo(startPath, signal);
|
|
1236
1237
|
if (info.ok && info.value.kind === "file")
|
|
1237
1238
|
return { note: "" };
|
|
@@ -1322,7 +1323,8 @@ export async function runGlobDetailed(env, root, pattern, opts = {}, signal) {
|
|
|
1322
1323
|
const t0 = Date.now();
|
|
1323
1324
|
const baseIgnore = await buildIgnore(env, root, signal);
|
|
1324
1325
|
const rootPrefix = root.replace(/[\\/]+$/, "") + (root.includes("\\") ? "\\" : "/");
|
|
1325
|
-
const
|
|
1326
|
+
const family = pathFamilyOf({ root });
|
|
1327
|
+
const start = opts.path ? (isAbsoluteForFamily(family, opts.path) ? opts.path : joinForFamily(family, root, opts.path)) : root;
|
|
1326
1328
|
if (opts.path !== undefined) {
|
|
1327
1329
|
const probe = await env.fileInfo(start, signal);
|
|
1328
1330
|
if (!probe.ok && probe.error.code === "not_found") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/core",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.17.0",
|
|
4
4
|
"description": "Stateless, task-oriented AI agent core",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BUSL-1.1",
|
|
@@ -83,7 +83,11 @@
|
|
|
83
83
|
"gate:tool-names": "node scripts/verify-tool-name-tables.mjs",
|
|
84
84
|
"gate:tool-docs": "node scripts/gen-tool-spec-fields.mjs --check",
|
|
85
85
|
"gate:backlog-ledger": "node scripts/verify-backlog-ledger.mjs",
|
|
86
|
-
"gate:
|
|
86
|
+
"gate:docs-tool-names": "node scripts/verify-docs-tool-names.mjs",
|
|
87
|
+
"gate:comment-ratio": "vitest run test/comment-ratio-ratchet.test.ts",
|
|
88
|
+
"gate:negative-controls": "vitest run test/gate-negative-controls.test.ts",
|
|
89
|
+
"gate:all": "node scripts/gate-all.mjs",
|
|
90
|
+
"car:gate": "node scripts/car-gate.mjs"
|
|
87
91
|
},
|
|
88
92
|
"dependencies": {
|
|
89
93
|
"@modelcontextprotocol/sdk": "1.30.0",
|