@arnilo/prism-coding-agent 0.0.23 → 0.0.24

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 CHANGED
@@ -1,11 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.0.24] - 2026-08-04
4
+
5
+ ### Added
6
+ - Durable `AgentEventSource` (memory + PostgreSQL LISTEN/NOTIFY), recoverable `ToolEffectStore`, and AG-UI MCP/MCP Apps/A2A fronting for Phase 7.
7
+
8
+ ### Changed
9
+ - Publishable graph remains **47** manifests at **0.0.24**; peers and lockfile move together.
10
+
11
+ See [migration guide](../../docs/migration.md) for the 0.0.23 → 0.0.24 notes.
12
+
3
13
  ## [0.0.23] - 2026-08-03
4
14
 
5
15
  ### Changed
6
16
  - Released with exact 0.0.23 graph.
7
17
 
8
-
9
18
  ## [0.0.22] - 2026-07-31
10
19
 
11
20
  ### Changed
@@ -22,14 +31,11 @@
22
31
  ### Changed
23
32
  - `createCodingTools` returns 9 tools; `createReadOnlyTools` returns 4 (includes `glob`).
24
33
 
25
-
26
-
27
34
  ## [0.0.20] - 2026-07-31
28
35
 
29
36
  ### Changed
30
37
  - Released with exact 0.0.20 graph.
31
38
 
32
-
33
39
  ## [0.0.19] - 2026-07-30
34
40
 
35
41
  ### Changed
@@ -1,4 +1,5 @@
1
1
  import { suspend, } from "@arnilo/prism-workflows";
2
+ import { CODING_OBSERVATION_EFFECT } from "./effects.js";
2
3
  import { enforceExecutionPolicy } from "./execution-policy.js";
3
4
  import { validateCodingLimit } from "./limits.js";
4
5
  export const ASK_USER_DECISION_TOOL_NAME = "ask_user_decision";
@@ -193,6 +194,7 @@ export function createAskUserDecisionTool(options) {
193
194
  const ask = options.ask;
194
195
  return {
195
196
  name: ASK_USER_DECISION_TOOL_NAME,
197
+ effect: CODING_OBSERVATION_EFFECT,
196
198
  description: "Ask the user to choose a direction when instructions are ambiguous and the choice matters. " +
197
199
  "Provide 2+ options; each option MUST include exactly 3 pros and 3 cons. " +
198
200
  'Use selectionMode "multiple" when several options may apply together; default is single choice. ' +
package/dist/checks.js CHANGED
@@ -4,6 +4,7 @@
4
4
  import { spawn } from "node:child_process";
5
5
  import { rm } from "node:fs/promises";
6
6
  import { isAbsolute } from "node:path";
7
+ import { CODING_UNSUPPORTED_EFFECT } from "./effects.js";
7
8
  import { enforceExecutionPolicy } from "./execution-policy.js";
8
9
  import { DEFAULT_CHECK_TIMEOUT_MS, DEFAULT_MAX_CHECK_CONCURRENCY, DEFAULT_MAX_CHECK_DIAGNOSTIC_LINES, DEFAULT_MAX_CHECK_NAMES, DEFAULT_MAX_CHECK_OUTPUT_BYTES, HARD_CHECK_TIMEOUT_MS, HARD_MAX_BYTES, HARD_MAX_CHECK_CONCURRENCY, HARD_MAX_CHECK_DIAGNOSTIC_LINES, HARD_MAX_CHECK_NAMES, HARD_MAX_CHECK_OUTPUT_BYTES, validateCodingLimit, } from "./limits.js";
9
10
  import { OutputAccumulator } from "./output-accumulator.js";
@@ -161,6 +162,7 @@ export function createCodingCheckTool(cwd, options) {
161
162
  const names = [...checks.keys()].sort();
162
163
  return {
163
164
  name: "coding_check",
165
+ effect: CODING_UNSUPPORTED_EFFECT,
164
166
  description: `Run a host-declared named check. Allowed names: ${names.join(", ")}. The model cannot choose executables or arguments.`,
165
167
  exclusive: true,
166
168
  parameters: {
package/dist/delete.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { lstat, readdir, rmdir, unlink } from "node:fs/promises";
2
+ import { CODING_LOCAL_EFFECT } from "./effects.js";
2
3
  import { enforceExecutionPolicy } from "./execution-policy.js";
3
4
  import { withFileMutationQueue } from "./file-mutation-queue.js";
4
5
  import { resolveContainedMutationPath } from "./mutation-path.js";
@@ -28,6 +29,7 @@ export function createDeleteTool(cwd, options) {
28
29
  const ops = options?.operations ?? defaultDeleteOperations;
29
30
  return {
30
31
  name: "delete",
32
+ effect: CODING_LOCAL_EFFECT,
31
33
  description: "High-risk: permanently delete a single file or empty directory in the workspace. Non-empty directories are rejected (no recursive delete). No trash/recycle — host undo is not automatic. Prefer edit/write when content can be fixed in place.",
32
34
  parameters: {
33
35
  type: "object",
package/dist/edit.js CHANGED
@@ -23,6 +23,7 @@ import { Buffer } from "node:buffer";
23
23
  import { constants } from "node:fs";
24
24
  import { access as fsAccess, stat as fsStat } from "node:fs/promises";
25
25
  import { atomicWriteUtf8File } from "./atomic-write.js";
26
+ import { CODING_LOCAL_EFFECT } from "./effects.js";
26
27
  import { readFileBounded } from "./bounded-file.js";
27
28
  import { applyEditsToNormalizedContent, detectLineEnding, generateDiffString, generateUnifiedPatch, normalizeToLF, restoreLineEndings, stripBom, } from "./edit-diff.js";
28
29
  import { enforceExecutionPolicy } from "./execution-policy.js";
@@ -95,6 +96,7 @@ export function createEditTool(cwd, options) {
95
96
  const maxEdits = validateCodingLimit("maxEdits", options?.maxEdits ?? DEFAULT_MAX_EDITS, HARD_MAX_EDITS);
96
97
  return {
97
98
  name: "edit",
99
+ effect: CODING_LOCAL_EFFECT,
98
100
  description: "Edit a single file using exact-then-fuzzy text replacement. Every edits[].oldText must match a unique, non-overlapping region of the original file. Exact match is tried first; if it fails, fuzzy match (unicode normalize + whitespace collapse) may still succeed silently — prefer exact oldText to avoid wrong-region edits. Duplicate/ambiguous matches fail closed and leave the file unchanged. If two changes affect the same block or nearby lines, merge them into one edit. Do not include large unchanged regions just to connect distant changes. When the host enabled requireReadBeforeWrite, read the path first or pass force=true.",
99
101
  parameters: {
100
102
  type: "object",
@@ -0,0 +1,33 @@
1
+ import type { JsonObject, ToolEffectDeclaration, ToolEffectRecord, ToolResult } from "@arnilo/prism";
2
+ export declare const CODING_OBSERVATION_EFFECT: {
3
+ readonly kind: "none";
4
+ readonly idempotency: "none";
5
+ };
6
+ export declare const CODING_LOCAL_EFFECT: {
7
+ readonly kind: "local_mutation";
8
+ readonly idempotency: "optional";
9
+ };
10
+ export declare const CODING_UNSUPPORTED_EFFECT: {
11
+ readonly kind: "external_mutation";
12
+ readonly idempotency: "unsupported";
13
+ };
14
+ export declare function classifyGitBranchEffect(args: JsonObject): ToolEffectDeclaration;
15
+ export declare function classifyGitWorktreeEffect(args: JsonObject): ToolEffectDeclaration;
16
+ export declare function classifyGitApplyEffect(args: JsonObject): ToolEffectDeclaration;
17
+ export interface CodingEffectReconciliationInput {
18
+ readonly cwd: string;
19
+ readonly record: Pick<ToolEffectRecord, "toolCallId" | "toolName">;
20
+ readonly args: JsonObject;
21
+ readonly signal?: AbortSignal;
22
+ }
23
+ export type CodingEffectReconciliation = {
24
+ readonly status: "completed";
25
+ readonly result: ToolResult;
26
+ } | {
27
+ readonly status: "unknown";
28
+ };
29
+ /**
30
+ * Checks only exact local postconditions. Callers retain the pending call arguments,
31
+ * then pass a completed result to ToolEffectStore.resolveUnknown() when this returns one.
32
+ */
33
+ export declare function reconcileCodingToolEffect(input: CodingEffectReconciliationInput): Promise<CodingEffectReconciliation>;
@@ -0,0 +1,89 @@
1
+ import { readFile, stat } from "node:fs/promises";
2
+ import { Buffer } from "node:buffer";
3
+ import { HARD_MAX_WRITE_BYTES } from "./limits.js";
4
+ import { resolveContainedMutationPath } from "./mutation-path.js";
5
+ export const CODING_OBSERVATION_EFFECT = { kind: "none", idempotency: "none" };
6
+ export const CODING_LOCAL_EFFECT = { kind: "local_mutation", idempotency: "optional" };
7
+ export const CODING_UNSUPPORTED_EFFECT = { kind: "external_mutation", idempotency: "unsupported" };
8
+ export function classifyGitBranchEffect(args) {
9
+ return args.action === "list" || args.action === "validate" ? CODING_OBSERVATION_EFFECT : CODING_LOCAL_EFFECT;
10
+ }
11
+ export function classifyGitWorktreeEffect(args) {
12
+ return args.action === "list" ? CODING_OBSERVATION_EFFECT : CODING_LOCAL_EFFECT;
13
+ }
14
+ export function classifyGitApplyEffect(args) {
15
+ return args.action === "check" ? CODING_OBSERVATION_EFFECT : CODING_LOCAL_EFFECT;
16
+ }
17
+ /**
18
+ * Checks only exact local postconditions. Callers retain the pending call arguments,
19
+ * then pass a completed result to ToolEffectStore.resolveUnknown() when this returns one.
20
+ */
21
+ export async function reconcileCodingToolEffect(input) {
22
+ input.signal?.throwIfAborted();
23
+ const completed = () => ({
24
+ status: "completed",
25
+ result: {
26
+ toolCallId: input.record.toolCallId,
27
+ name: input.record.toolName,
28
+ content: [{ type: "text", text: "Local tool postcondition verified." }],
29
+ value: { reconciled: true },
30
+ },
31
+ });
32
+ try {
33
+ if (input.record.toolName === "write") {
34
+ const path = stringArg(input.args, "path");
35
+ const content = stringArg(input.args, "content");
36
+ if (path === undefined || content === undefined || Buffer.byteLength(content, "utf8") > HARD_MAX_WRITE_BYTES)
37
+ return { status: "unknown" };
38
+ const target = await resolveContainedMutationPath(input.cwd, path);
39
+ const expected = Buffer.from(content, "utf8");
40
+ if ((await stat(target)).size !== expected.length)
41
+ return { status: "unknown" };
42
+ return (await readFile(target)).equals(expected) ? completed() : { status: "unknown" };
43
+ }
44
+ if (input.record.toolName === "delete") {
45
+ const path = stringArg(input.args, "path");
46
+ if (path === undefined)
47
+ return { status: "unknown" };
48
+ await resolveContainedMutationPath(input.cwd, path, { allowMissing: true });
49
+ return (await missing(input.cwd, path)) ? completed() : { status: "unknown" };
50
+ }
51
+ if (input.record.toolName === "move") {
52
+ const from = stringArg(input.args, "from");
53
+ const to = stringArg(input.args, "to");
54
+ if (from === undefined || to === undefined)
55
+ return { status: "unknown" };
56
+ await Promise.all([
57
+ resolveContainedMutationPath(input.cwd, from, { allowMissing: true }),
58
+ resolveContainedMutationPath(input.cwd, to, { allowMissing: true }),
59
+ ]);
60
+ if (await missing(input.cwd, from)) {
61
+ try {
62
+ await resolveContainedMutationPath(input.cwd, to);
63
+ return completed();
64
+ }
65
+ catch {
66
+ return { status: "unknown" };
67
+ }
68
+ }
69
+ }
70
+ }
71
+ catch {
72
+ // Containment/read failure is not proof that an effect did not happen.
73
+ }
74
+ return { status: "unknown" };
75
+ }
76
+ function stringArg(args, name) {
77
+ const value = args[name];
78
+ return typeof value === "string" ? value : undefined;
79
+ }
80
+ async function missing(cwd, path) {
81
+ try {
82
+ await resolveContainedMutationPath(cwd, path);
83
+ return false;
84
+ }
85
+ catch (error) {
86
+ return error.code === "ENOENT" || error.code === "ENOTDIR";
87
+ }
88
+ }
89
+ //# sourceMappingURL=effects.js.map
package/dist/git-tools.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { createCodingCheckTool } from "./checks.js";
2
+ import { classifyGitApplyEffect, classifyGitBranchEffect, classifyGitWorktreeEffect, CODING_LOCAL_EFFECT, CODING_OBSERVATION_EFFECT, CODING_UNSUPPORTED_EFFECT, } from "./effects.js";
2
3
  import { enforceExecutionPolicy } from "./execution-policy.js";
3
4
  import { createGitOperations, GitError, } from "./git.js";
4
5
  function errorResult(toolName, toolCallId, message) {
@@ -39,6 +40,7 @@ export function createGitStatusTool(cwd, options) {
39
40
  const getOps = opsFactory(cwd, options);
40
41
  return {
41
42
  name: "git_status",
43
+ effect: CODING_OBSERVATION_EFFECT,
42
44
  description: "Return structured Git status (porcelain v2) including branch metadata and dirty-state. Does not follow shell; paths are repository-relative.",
43
45
  parameters: {
44
46
  type: "object",
@@ -84,6 +86,7 @@ export function createGitDiffTool(cwd, options) {
84
86
  const getOps = opsFactory(cwd, options);
85
87
  return {
86
88
  name: "git_diff",
89
+ effect: CODING_OBSERVATION_EFFECT,
87
90
  description: "Return a bounded unified diff (--no-ext-diff --no-textconv). Large diffs truncate inline and may spill through the host artifact writer.",
88
91
  parameters: {
89
92
  type: "object",
@@ -138,6 +141,7 @@ export function createGitBranchTool(cwd, options) {
138
141
  const getOps = opsFactory(cwd, options);
139
142
  return {
140
143
  name: "git_branch",
144
+ effect: classifyGitBranchEffect,
141
145
  description: "Validate, list, create, or switch branches. Switch refuses a dirty worktree unless createCheckpoint=true.",
142
146
  exclusive: true,
143
147
  parameters: {
@@ -208,6 +212,7 @@ export function createGitWorktreeTool(cwd, options) {
208
212
  const getOps = opsFactory(cwd, options);
209
213
  return {
210
214
  name: "git_worktree",
215
+ effect: classifyGitWorktreeEffect,
211
216
  description: "List, add, or remove Git worktrees within finite caps. Prefer disposable worktrees for mutating transactions.",
212
217
  exclusive: true,
213
218
  parameters: {
@@ -269,6 +274,7 @@ export function createGitApplyTool(cwd, options) {
269
274
  const getOps = opsFactory(cwd, options);
270
275
  return {
271
276
  name: "git_apply",
277
+ effect: classifyGitApplyEffect,
272
278
  description: "Check, apply, or reverse a unified patch. Always runs apply --check first for apply/reverse. Dirty trees require createCheckpoint=true; failures restore the checkpoint or clean tree.",
273
279
  exclusive: true,
274
280
  parameters: {
@@ -336,6 +342,7 @@ export function createGitCommitTool(cwd, options) {
336
342
  const getOps = opsFactory(cwd, options);
337
343
  return {
338
344
  name: "git_commit",
345
+ effect: CODING_LOCAL_EFFECT,
339
346
  description: "Stage and commit explicit paths only (never `git add -A`). Refuses dirty unrelated worktrees unless createCheckpoint=true. Uses --no-verify and a temp message file; never pushes.",
340
347
  exclusive: true,
341
348
  parameters: {
@@ -395,6 +402,7 @@ export function createGitPrHandoffTool(cwd, options) {
395
402
  const getOps = opsFactory(cwd, options);
396
403
  return {
397
404
  name: "git_pr_handoff",
405
+ effect: CODING_UNSUPPORTED_EFFECT,
398
406
  description: "Build a bounded host-owned PR handoff payload (base/head/commits/paths/diffstat/checks/artifact). Never pushes, authenticates, or opens a PR.",
399
407
  exclusive: true,
400
408
  parameters: {
package/dist/glob.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { CODING_OBSERVATION_EFFECT } from "./effects.js";
1
2
  import { enforceExecutionPolicy } from "./execution-policy.js";
2
3
  import { HARD_MAX_REPO_DEPTH, HARD_MAX_REPO_RESULTS, validateCodingLimit, validateCodingLimitAllowZero } from "./limits.js";
3
4
  import { createLocalRepositoryOperations, RepositoryError, resolveRepositoryLimits, } from "./repository.js";
@@ -30,6 +31,7 @@ export function createGlobTool(cwd, options) {
30
31
  const ops = options?.operations ?? createLocalRepositoryOperations(limits);
31
32
  return {
32
33
  name: "glob",
34
+ effect: CODING_OBSERVATION_EFFECT,
33
35
  description: `Find workspace files by glob pattern without shell find. Supports * (segment), ? (one char), and ** (directories). Brace expansion is rejected. Skips hidden names and excluded basenames (default: ${limits.exclude.join(", ")}) unless overridden. Does not follow symlinks. Results paginate with offset/maxResults (default ${limits.maxResults}). Depth default ${limits.maxDepth}. Prefer repo_list to enumerate directories and repo_search to find text inside files.`,
34
36
  parameters: {
35
37
  type: "object",
package/dist/index.d.ts CHANGED
@@ -9,6 +9,8 @@ export type { CodingArtifactKind, CodingArtifactRef, CodingCheckpointLimitOption
9
9
  export { assertCodingResumeAllowed, buildCodingCheckpointMetadata, CODING_CHECKPOINT_SCHEMA_VERSION, CODING_STATE_KEY, CodingCheckpointError, codingCheckpointStatePatch, codingPlanPathForTask, createCodingArtifactRef, createCodingPlanMarkdown, fingerprintJson, parseCodingPlanTodos, readCodingCheckpointFromState, readCodingPlanFile, resolveCodingCheckpointLimits, validateCodingCheckpointMetadata, verifyCodingArtifactBytes, writeCodingPlanFile, } from "./coding-checkpoint.js";
10
10
  export type { Edit, EditOperations, EditToolDetails, EditToolOptions } from "./edit.js";
11
11
  export { createEditTool } from "./edit.js";
12
+ export type { CodingEffectReconciliation, CodingEffectReconciliationInput } from "./effects.js";
13
+ export { classifyGitApplyEffect, classifyGitBranchEffect, classifyGitWorktreeEffect, CODING_LOCAL_EFFECT, CODING_OBSERVATION_EFFECT, CODING_UNSUPPORTED_EFFECT, reconcileCodingToolEffect, } from "./effects.js";
12
14
  export type { ArtifactReference, ArtifactWriter, BoundGitRunner, CreateGitOperationsOptions, CreateGitRunnerOptions, GitExecRequest, GitExecResult, GitLimitOptions, GitOperations, GitRunner, GitStatusBranch, GitStatusEntry, GitStatusEntryKind, GitStatusResult, PrHandoff, ResolvedGitLimits, } from "./git.js";
13
15
  export { createBoundGitRunner, createGitOperations, GitError, parsePorcelainV2, resolveGitLimits, runGitCli, SAFE_GIT_CONFIG_ARGS, SAFE_GIT_ENV, } from "./git.js";
14
16
  export type { GitToolsOptions } from "./git-tools.js";
package/dist/index.js CHANGED
@@ -10,6 +10,7 @@ export { createCodingCheckTool } from "./checks.js";
10
10
  export { createDeleteTool } from "./delete.js";
11
11
  export { assertCodingResumeAllowed, buildCodingCheckpointMetadata, CODING_CHECKPOINT_SCHEMA_VERSION, CODING_STATE_KEY, CodingCheckpointError, codingCheckpointStatePatch, codingPlanPathForTask, createCodingArtifactRef, createCodingPlanMarkdown, fingerprintJson, parseCodingPlanTodos, readCodingCheckpointFromState, readCodingPlanFile, resolveCodingCheckpointLimits, validateCodingCheckpointMetadata, verifyCodingArtifactBytes, writeCodingPlanFile, } from "./coding-checkpoint.js";
12
12
  export { createEditTool } from "./edit.js";
13
+ export { classifyGitApplyEffect, classifyGitBranchEffect, classifyGitWorktreeEffect, CODING_LOCAL_EFFECT, CODING_OBSERVATION_EFFECT, CODING_UNSUPPORTED_EFFECT, reconcileCodingToolEffect, } from "./effects.js";
13
14
  export { createBoundGitRunner, createGitOperations, GitError, parsePorcelainV2, resolveGitLimits, runGitCli, SAFE_GIT_CONFIG_ARGS, SAFE_GIT_ENV, } from "./git.js";
14
15
  export { createGitApplyTool, createGitBranchTool, createGitCommitTool, createGitDiffTool, createGitPrHandoffTool, createGitStatusTool, createGitTools, createGitWorktreeTool, } from "./git-tools.js";
15
16
  export { CODING_GOAL_VERIFY_REVISION, CODING_GOAL_VERIFY_SUSPEND_REASON, CODING_GOAL_VERIFY_WORKFLOW_ID, CodingGoalVerifyError, createCodingGoalVerifyWorkflow, runCodingGoalVerify, } from "./goal-verify.js";
package/dist/list.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { CODING_OBSERVATION_EFFECT } from "./effects.js";
1
2
  import { enforceExecutionPolicy } from "./execution-policy.js";
2
3
  import { HARD_MAX_REPO_DEPTH, HARD_MAX_REPO_RESULTS, validateCodingLimit, validateCodingLimitAllowZero } from "./limits.js";
3
4
  import { createLocalRepositoryOperations, RepositoryError, resolveRepositoryLimits, } from "./repository.js";
@@ -33,6 +34,7 @@ export function createRepoListTool(cwd, options) {
33
34
  const ops = options?.operations ?? createLocalRepositoryOperations(limits);
34
35
  return {
35
36
  name: "repo_list",
37
+ effect: CODING_OBSERVATION_EFFECT,
36
38
  description: `List repository entries under the workspace with deterministic relative paths. Prefer glob when you already know a filename pattern (*.ts, **/src/**). Prefer repo_search to find text inside files. Skips hidden names and excluded basenames (default: ${limits.exclude.join(", ")}) unless overridden. Does not follow symlinks. Results paginate with offset/maxResults (default ${limits.maxResults}). Depth default ${limits.maxDepth}.`,
37
39
  parameters: {
38
40
  type: "object",
package/dist/move.js CHANGED
@@ -2,6 +2,7 @@ import { access, constants } from "node:fs";
2
2
  import { dirname } from "node:path";
3
3
  import { lstat, rename, unlink } from "node:fs/promises";
4
4
  import { promisify } from "node:util";
5
+ import { CODING_LOCAL_EFFECT } from "./effects.js";
5
6
  import { enforceExecutionPolicy } from "./execution-policy.js";
6
7
  import { withFileMutationQueue } from "./file-mutation-queue.js";
7
8
  import { resolveContainedMutationPath } from "./mutation-path.js";
@@ -36,6 +37,7 @@ export function createMoveTool(cwd, options) {
36
37
  const ops = options?.operations ?? defaultMoveOperations;
37
38
  return {
38
39
  name: "move",
40
+ effect: CODING_LOCAL_EFFECT,
39
41
  description: "High-risk: move or rename a file within the workspace. Set overwrite=true to replace an existing destination file only (directories/non-empty dest rejected). Does not create parent directories. No trash — host undo is not automatic.",
40
42
  parameters: {
41
43
  type: "object",
package/dist/read.js CHANGED
@@ -23,6 +23,7 @@ import { Buffer } from "node:buffer";
23
23
  import { constants } from "node:fs";
24
24
  import { access as fsAccess, stat as fsStat, open } from "node:fs/promises";
25
25
  import { readFileBounded } from "./bounded-file.js";
26
+ import { CODING_OBSERVATION_EFFECT } from "./effects.js";
26
27
  import { enforceExecutionPolicy } from "./execution-policy.js";
27
28
  import { DEFAULT_MAX_BYTES, DEFAULT_MAX_IMAGE_BYTES, DEFAULT_MAX_LINES, DEFAULT_MAX_TEXT_SCAN_BYTES, HARD_MAX_BYTES, HARD_MAX_IMAGE_BYTES, HARD_MAX_LINES, HARD_MAX_TEXT_SCAN_BYTES, validateCodingLimit, } from "./limits.js";
28
29
  import { resolveReadPathAsync } from "./path-utils.js";
@@ -307,6 +308,7 @@ export function createReadTool(cwd, options) {
307
308
  const maxScanBytes = validateCodingLimit("maxScanBytes", options?.maxScanBytes ?? DEFAULT_MAX_TEXT_SCAN_BYTES, HARD_MAX_TEXT_SCAN_BYTES);
308
309
  return {
309
310
  name: "read",
311
+ effect: CODING_OBSERVATION_EFFECT,
310
312
  description: `Read the contents of a file. Supports text files and images (jpg, png, gif, webp, bmp); images are returned as image content. For text files, output is truncated to ${maxLines} lines or ${maxBytes / 1024}KB (whichever is hit first). When truncated, continue with the suggested offset until complete. Prefer repo_search to find text across many files.`,
311
313
  parameters: {
312
314
  type: "object",
package/dist/search.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { CODING_OBSERVATION_EFFECT } from "./effects.js";
1
2
  import { enforceExecutionPolicy } from "./execution-policy.js";
2
3
  import { HARD_MAX_SEARCH_CONTEXT_LINES, HARD_MAX_SEARCH_MATCHES, validateCodingLimit, validateCodingLimitAllowZero } from "./limits.js";
3
4
  import { createLocalRepositoryOperations, RepositoryError, resolveRepositoryLimits, } from "./repository.js";
@@ -101,6 +102,7 @@ export function createRepoSearchTool(cwd, options) {
101
102
  const ops = options?.operations ?? createLocalRepositoryOperations(limits);
102
103
  return {
103
104
  name: "repo_search",
105
+ effect: CODING_OBSERVATION_EFFECT,
104
106
  description: `Search text files under the workspace using literal substring match. Use outputMode "files_with_matches" for paths only or "count" for totals without line bodies. Skips binary files, excluded basenames (default: ${limits.exclude.join(", ")}), and hidden names unless includeHidden is true. Does not follow symlinks. Caps matches/scanned bytes/time.`,
105
107
  parameters: {
106
108
  type: "object",
package/dist/shell.js CHANGED
@@ -23,6 +23,7 @@
23
23
  import { spawn } from "node:child_process";
24
24
  import { constants, existsSync } from "node:fs";
25
25
  import { access as fsAccess } from "node:fs/promises";
26
+ import { CODING_UNSUPPORTED_EFFECT } from "./effects.js";
26
27
  import { enforceExecutionPolicy } from "./execution-policy.js";
27
28
  import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, DEFAULT_MAX_TOTAL_OUTPUT_BYTES, DEFAULT_SHELL_TIMEOUT_SECONDS, HARD_MAX_BYTES, HARD_MAX_LINES, HARD_MAX_TOTAL_OUTPUT_BYTES, HARD_SHELL_TIMEOUT_SECONDS, validateCodingLimit, } from "./limits.js";
28
29
  import { OutputAccumulator } from "./output-accumulator.js";
@@ -264,6 +265,7 @@ export function createShellTool(cwd, options) {
264
265
  const tempFilePrefix = options?.tempFilePrefix ?? "prism-shell";
265
266
  return {
266
267
  name: "shell",
268
+ effect: CODING_UNSUPPORTED_EFFECT,
267
269
  exclusive: true,
268
270
  description: `Execute a shell command in the current working directory. Returns combined stdout and stderr. Prefer repo_list, repo_search, glob, read, write, edit, delete, or move for those jobs — use shell only when no dedicated tool fits. Output is truncated to the last ${maxLines} lines or ${maxBytes / 1024}KB and capped at ${formatSize(maxTotalOutputBytes)} total. Truncated successful output is saved to a temp file. Timeout defaults to ${defaultTimeout} seconds.`,
269
271
  parameters: {
package/dist/write.js CHANGED
@@ -18,6 +18,7 @@ import { Buffer } from "node:buffer";
18
18
  import { mkdir as fsMkdir } from "node:fs/promises";
19
19
  import { dirname } from "node:path";
20
20
  import { atomicWriteUtf8File } from "./atomic-write.js";
21
+ import { CODING_LOCAL_EFFECT } from "./effects.js";
21
22
  import { enforceExecutionPolicy } from "./execution-policy.js";
22
23
  import { withFileMutationQueue } from "./file-mutation-queue.js";
23
24
  import { DEFAULT_MAX_WRITE_BYTES, HARD_MAX_WRITE_BYTES, validateCodingLimit } from "./limits.js";
@@ -45,6 +46,7 @@ export function createWriteTool(cwd, options) {
45
46
  const maxInputBytes = validateCodingLimit("maxInputBytes", options?.maxInputBytes ?? DEFAULT_MAX_WRITE_BYTES, HARD_MAX_WRITE_BYTES);
46
47
  return {
47
48
  name: "write",
49
+ effect: CODING_LOCAL_EFFECT,
48
50
  description: "Create or overwrite a file (full replace). Creates parent directories. Prefer edit for targeted changes. When the host enabled requireReadBeforeWrite, read the path first or pass force=true to override.",
49
51
  parameters: {
50
52
  type: "object",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arnilo/prism-coding-agent",
3
- "version": "0.0.23",
3
+ "version": "0.0.24",
4
4
  "description": "Optional coding-agent tools (shell, read, write, edit, repo_list, repo_search, glob, delete, move, opt-in Git/check/ask-user-decision, and durable plan/checkpoint helpers) package for Prism.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -28,8 +28,8 @@
28
28
  "diff": "^9.0.0"
29
29
  },
30
30
  "peerDependencies": {
31
- "@arnilo/prism": "0.0.23",
32
- "@arnilo/prism-workflows": "0.0.23"
31
+ "@arnilo/prism": "0.0.24",
32
+ "@arnilo/prism-workflows": "0.0.24"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@arnilo/prism": "file:../..",