ultimate-pi 0.20.0 → 0.22.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.
Files changed (130) hide show
  1. package/.agents/skills/harness-decisions/SKILL.md +68 -2
  2. package/.agents/skills/harness-git-commit/SKILL.md +72 -0
  3. package/.agents/skills/harness-governor/SKILL.md +2 -2
  4. package/.agents/skills/harness-ls-lint-setup/SKILL.md +59 -0
  5. package/.agents/skills/harness-plan/SKILL.md +13 -11
  6. package/.agents/skills/harness-review/SKILL.md +1 -1
  7. package/.agents/skills/harness-sentrux-repair/SKILL.md +48 -0
  8. package/.agents/skills/sentrux/SKILL.md +4 -2
  9. package/.agents/skills/wiki-save/SKILL.md +1 -1
  10. package/.pi/PACKAGING.md +6 -0
  11. package/.pi/SYSTEM.md +21 -3
  12. package/.pi/agents/harness/ls-lint-steward.md +49 -0
  13. package/.pi/agents/harness/planning/decompose.md +4 -4
  14. package/.pi/agents/harness/reviewing/evaluator.md +1 -1
  15. package/.pi/agents/harness/running/executor.md +1 -1
  16. package/.pi/agents/harness/sentrux-repair-advisor.md +50 -0
  17. package/.pi/agents/pi-pi/prompt-expert.md +17 -2
  18. package/.pi/auto-commit.json +9 -2
  19. package/.pi/extensions/debate-orchestrator.ts +3 -0
  20. package/.pi/extensions/harness-anchored-edit.ts +7 -9
  21. package/.pi/extensions/harness-ask-user.ts +13 -34
  22. package/.pi/extensions/harness-debate-tools.ts +43 -4
  23. package/.pi/extensions/harness-live-widget.ts +28 -19
  24. package/.pi/extensions/harness-run-context.ts +278 -115
  25. package/.pi/extensions/harness-web-tools.ts +598 -471
  26. package/.pi/extensions/ls-lint-rules-sync.ts +103 -0
  27. package/.pi/extensions/observation-bus.ts +4 -0
  28. package/.pi/extensions/policy-gate.ts +270 -229
  29. package/.pi/extensions/sentrux-rules-sync.ts +2 -0
  30. package/.pi/extensions/soundboard.ts +48 -48
  31. package/.pi/harness/README.md +4 -0
  32. package/.pi/harness/agents.manifest.json +15 -7
  33. package/.pi/harness/agents.policy.yaml +49 -82
  34. package/.pi/harness/docs/adrs/0052-ls-lint-naming-lifecycle.md +45 -0
  35. package/.pi/harness/docs/adrs/0052-sentrux-structured-repair.md +38 -0
  36. package/.pi/harness/docs/adrs/0053-plan-task-clarification-gate.md +39 -0
  37. package/.pi/harness/docs/adrs/0054-harness-native-ask-user.md +40 -0
  38. package/.pi/harness/docs/adrs/0055-auto-commit-coauthor-lifecycle.md +40 -0
  39. package/.pi/harness/docs/adrs/README.md +5 -0
  40. package/.pi/harness/docs/practice-map.md +10 -5
  41. package/.pi/harness/evals/smoke/ls-lint-stub.json +10 -0
  42. package/.pi/harness/evolution/self-healing-rules.json +16 -0
  43. package/.pi/harness/ls-lint/naming.manifest.json +128 -0
  44. package/.pi/harness/sentrux/architecture.manifest.json +1 -1
  45. package/.pi/harness/specs/auto-commit.schema.json +63 -0
  46. package/.pi/harness/specs/ls-lint-manifest-proposal.schema.json +80 -0
  47. package/.pi/harness/specs/ls-lint-signal.schema.json +47 -0
  48. package/.pi/harness/specs/naming-manifest.schema.json +54 -0
  49. package/.pi/harness/specs/plan-task-clarification.schema.json +88 -0
  50. package/.pi/harness/specs/sentrux-diagnostics.schema.json +173 -0
  51. package/.pi/harness/specs/sentrux-repair-plan.schema.json +133 -0
  52. package/.pi/harness/specs/sentrux-report.schema.json +119 -0
  53. package/.pi/harness/specs/sentrux-signal.schema.json +34 -1
  54. package/.pi/lib/agents-policy.d.mts +26 -51
  55. package/.pi/lib/agents-policy.mjs +41 -28
  56. package/.pi/lib/agt/build-evaluation-context.ts +136 -64
  57. package/.pi/lib/ask-user/constants.mjs +3 -0
  58. package/.pi/lib/ask-user/constants.ts +4 -0
  59. package/.pi/lib/ask-user/contracts/glimpse-parse.ts +56 -0
  60. package/.pi/lib/ask-user/contracts/glimpse-payload-build.ts +58 -0
  61. package/.pi/lib/ask-user/contracts/glimpse-payload.ts +38 -0
  62. package/.pi/lib/ask-user/core/questionnaire.ts +74 -0
  63. package/.pi/lib/ask-user/dialog.ts +2 -314
  64. package/.pi/lib/ask-user/fallback.ts +2 -78
  65. package/.pi/lib/ask-user/format.ts +85 -0
  66. package/.pi/lib/ask-user/glimpseui.d.ts +10 -0
  67. package/.pi/lib/ask-user/index.ts +114 -0
  68. package/.pi/lib/ask-user/merge-task-clarification.ts +98 -0
  69. package/.pi/lib/ask-user/policy.mjs +43 -0
  70. package/.pi/lib/ask-user/policy.ts +104 -0
  71. package/.pi/lib/ask-user/presenters/glimpse.ts +130 -0
  72. package/.pi/lib/ask-user/presenters/headless.ts +131 -0
  73. package/.pi/lib/ask-user/presenters/select.ts +60 -0
  74. package/.pi/lib/ask-user/presenters/tui.ts +373 -0
  75. package/.pi/lib/ask-user/presenters/types.ts +13 -0
  76. package/.pi/lib/ask-user/render.ts +40 -9
  77. package/.pi/lib/ask-user/schema.ts +66 -13
  78. package/.pi/lib/ask-user/types.ts +60 -3
  79. package/.pi/lib/ask-user/validate-core.mjs +193 -7
  80. package/.pi/lib/ask-user/validate.ts +53 -34
  81. package/.pi/lib/harness-anchored-edit/package.json +3 -0
  82. package/.pi/lib/harness-artifact-gate.ts +75 -21
  83. package/.pi/lib/harness-auto-commit-config.mjs +321 -0
  84. package/.pi/lib/harness-lens/clients/lsp/client.ts +62 -39
  85. package/.pi/lib/harness-lens/clients/tool-policy.ts +73 -181
  86. package/.pi/lib/harness-lens/index.ts +241 -108
  87. package/.pi/lib/harness-lens/tools/lsp-navigation.ts +10 -8
  88. package/.pi/lib/harness-repair-brief.ts +84 -25
  89. package/.pi/lib/harness-run-context.ts +42 -52
  90. package/.pi/lib/harness-sentrux-parse.mjs +272 -0
  91. package/.pi/lib/harness-sentrux-root.mjs +78 -0
  92. package/.pi/lib/harness-slash-completions.ts +116 -0
  93. package/.pi/lib/harness-spawn-topology.ts +121 -87
  94. package/.pi/lib/harness-subagent-submit-registry.ts +10 -0
  95. package/.pi/lib/harness-subagents-bridge.ts +4 -1
  96. package/.pi/lib/harness-ui-state.ts +95 -48
  97. package/.pi/lib/plan-approval/dialog.ts +5 -0
  98. package/.pi/lib/plan-approval/validate.ts +1 -1
  99. package/.pi/lib/plan-approval-readiness.ts +32 -0
  100. package/.pi/lib/plan-debate-gate.ts +154 -114
  101. package/.pi/lib/plan-task-clarification.ts +158 -0
  102. package/.pi/prompts/harness-auto.md +2 -2
  103. package/.pi/prompts/harness-ls-lint-steward.md +43 -0
  104. package/.pi/prompts/harness-plan.md +58 -8
  105. package/.pi/prompts/harness-review.md +40 -6
  106. package/.pi/prompts/harness-run.md +33 -11
  107. package/.pi/prompts/harness-setup.md +72 -3
  108. package/.pi/prompts/harness-steer.md +2 -1
  109. package/.pi/prompts/wiki-save.md +5 -4
  110. package/.pi/scripts/README.md +8 -0
  111. package/.pi/scripts/generate-agents-policy-yaml.mjs +14 -2
  112. package/.pi/scripts/harness-auto-commit-bootstrap.mjs +96 -0
  113. package/.pi/scripts/harness-cli-verify.sh +47 -0
  114. package/.pi/scripts/harness-git-churn.mjs +77 -0
  115. package/.pi/scripts/harness-git-commit.mjs +173 -0
  116. package/.pi/scripts/harness-ls-lint-bootstrap.mjs +142 -0
  117. package/.pi/scripts/harness-ls-lint-cli.mjs +184 -0
  118. package/.pi/scripts/harness-seed-project-contracts.mjs +47 -0
  119. package/.pi/scripts/harness-sentrux-diagnostics.mjs +230 -0
  120. package/.pi/scripts/harness-sentrux-report.mjs +256 -0
  121. package/.pi/scripts/harness-verify.mjs +288 -125
  122. package/.pi/scripts/ls-lint-rules-sync.mjs +265 -0
  123. package/.pi/scripts/run-tests.mjs +1 -0
  124. package/.pi/settings.example.json +1 -0
  125. package/.sentrux/rules.toml +1 -1
  126. package/AGENTS.md +1 -0
  127. package/CHANGELOG.md +25 -0
  128. package/README.md +13 -4
  129. package/package.json +5 -1
  130. package/vendor/pi-vcc/src/hooks/before-compact.ts +86 -60
@@ -0,0 +1,133 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://ultimate-pi.local/.pi/harness/specs/sentrux-repair-plan.schema.json",
4
+ "title": "SentruxRepairPlan",
5
+ "description": "Actionable repair plan from sentrux-repair-advisor (steer/executor input).",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": [
9
+ "schema_version",
10
+ "status",
11
+ "summary",
12
+ "bottleneck",
13
+ "root_causes",
14
+ "actions",
15
+ "human_required"
16
+ ],
17
+ "properties": {
18
+ "schema_version": {
19
+ "type": "string",
20
+ "const": "1.0.0"
21
+ },
22
+ "status": {
23
+ "type": "string",
24
+ "enum": ["ok", "partial", "blocked"]
25
+ },
26
+ "summary": {
27
+ "type": "string",
28
+ "minLength": 1
29
+ },
30
+ "bottleneck": {
31
+ "type": "string",
32
+ "enum": [
33
+ "modularity",
34
+ "equality",
35
+ "acyclicity",
36
+ "stability",
37
+ "abstraction"
38
+ ]
39
+ },
40
+ "root_causes": {
41
+ "type": "array",
42
+ "items": { "type": "string", "minLength": 1 },
43
+ "minItems": 1
44
+ },
45
+ "actions": {
46
+ "type": "array",
47
+ "minItems": 1,
48
+ "items": { "$ref": "#/$defs/action" }
49
+ },
50
+ "do_not_touch": {
51
+ "type": "array",
52
+ "items": { "type": "string" }
53
+ },
54
+ "verification": {
55
+ "type": "array",
56
+ "items": { "type": "string" }
57
+ },
58
+ "human_required": {
59
+ "type": "boolean"
60
+ },
61
+ "open_questions": {
62
+ "type": "array",
63
+ "items": { "type": "string" }
64
+ },
65
+ "evidence": {
66
+ "type": "array",
67
+ "items": {
68
+ "type": "object",
69
+ "additionalProperties": false,
70
+ "required": ["source", "ref", "summary"],
71
+ "properties": {
72
+ "source": {
73
+ "type": "string",
74
+ "enum": [
75
+ "sentrux-report",
76
+ "sentrux-diagnostics",
77
+ "graphify",
78
+ "plan",
79
+ "diff"
80
+ ]
81
+ },
82
+ "ref": { "type": "string", "minLength": 1 },
83
+ "summary": { "type": "string", "minLength": 1 }
84
+ }
85
+ }
86
+ }
87
+ },
88
+ "$defs": {
89
+ "action": {
90
+ "type": "object",
91
+ "additionalProperties": false,
92
+ "required": ["id", "priority", "kind", "target", "instruction"],
93
+ "properties": {
94
+ "id": {
95
+ "type": "string",
96
+ "pattern": "^[a-z][a-z0-9-]*$"
97
+ },
98
+ "priority": {
99
+ "type": "integer",
100
+ "minimum": 1,
101
+ "maximum": 10
102
+ },
103
+ "kind": {
104
+ "type": "string",
105
+ "enum": [
106
+ "refactor",
107
+ "extract",
108
+ "move",
109
+ "split",
110
+ "tune",
111
+ "document",
112
+ "defer"
113
+ ]
114
+ },
115
+ "target": {
116
+ "type": "string",
117
+ "minLength": 1
118
+ },
119
+ "instruction": {
120
+ "type": "string",
121
+ "minLength": 1
122
+ },
123
+ "acceptance": {
124
+ "type": "string"
125
+ },
126
+ "rule_ids": {
127
+ "type": "array",
128
+ "items": { "type": "string" }
129
+ }
130
+ }
131
+ }
132
+ }
133
+ }
@@ -0,0 +1,119 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://ultimate-pi.local/.pi/harness/specs/sentrux-report.schema.json",
4
+ "title": "SentruxReport",
5
+ "description": "Single-scan Sentrux check + gate capture for a harness run (OSS CLI parse).",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": [
9
+ "schema_version",
10
+ "captured_at",
11
+ "project_root",
12
+ "parser_version",
13
+ "check",
14
+ "gate"
15
+ ],
16
+ "properties": {
17
+ "schema_version": {
18
+ "type": "string",
19
+ "const": "1.0.0"
20
+ },
21
+ "captured_at": {
22
+ "type": "string",
23
+ "format": "date-time"
24
+ },
25
+ "project_root": {
26
+ "type": "string",
27
+ "minLength": 1
28
+ },
29
+ "parser_version": {
30
+ "type": "string",
31
+ "minLength": 1
32
+ },
33
+ "sentrux_cli_version": {
34
+ "type": ["string", "null"]
35
+ },
36
+ "upstream_json_available": {
37
+ "type": "boolean"
38
+ },
39
+ "check": {
40
+ "type": "object",
41
+ "additionalProperties": false,
42
+ "required": ["parse_ok", "check_pass", "violations"],
43
+ "properties": {
44
+ "parse_ok": { "type": "boolean" },
45
+ "parse_errors": {
46
+ "type": "array",
47
+ "items": { "type": "string" }
48
+ },
49
+ "rules_checked": { "type": ["integer", "null"] },
50
+ "quality_signal": { "type": ["integer", "null"] },
51
+ "check_pass": { "type": "boolean" },
52
+ "violations": {
53
+ "type": "array",
54
+ "items": { "$ref": "#/$defs/violation" }
55
+ },
56
+ "stdout_sha256": { "type": "string" }
57
+ }
58
+ },
59
+ "gate": {
60
+ "type": "object",
61
+ "additionalProperties": false,
62
+ "required": ["parse_ok", "status", "degraded_reasons"],
63
+ "properties": {
64
+ "parse_ok": { "type": "boolean" },
65
+ "parse_errors": {
66
+ "type": "array",
67
+ "items": { "type": "string" }
68
+ },
69
+ "status": {
70
+ "type": "string",
71
+ "enum": ["pass", "degraded", "unknown"]
72
+ },
73
+ "quality_before": { "type": ["integer", "null"] },
74
+ "quality_after": { "type": ["integer", "null"] },
75
+ "metrics": {
76
+ "type": "array",
77
+ "items": {
78
+ "type": "object",
79
+ "additionalProperties": false,
80
+ "required": ["name", "before", "after"],
81
+ "properties": {
82
+ "name": { "type": "string" },
83
+ "before": { "type": "string" },
84
+ "after": { "type": "string" }
85
+ }
86
+ }
87
+ },
88
+ "degraded_reasons": {
89
+ "type": "array",
90
+ "items": { "type": "string" }
91
+ },
92
+ "stdout_sha256": { "type": "string" }
93
+ }
94
+ }
95
+ },
96
+ "$defs": {
97
+ "violation": {
98
+ "type": "object",
99
+ "additionalProperties": false,
100
+ "required": ["severity", "rule", "message", "files"],
101
+ "properties": {
102
+ "severity": {
103
+ "type": "string",
104
+ "enum": ["error", "warning", "info"]
105
+ },
106
+ "rule": { "type": "string", "minLength": 1 },
107
+ "message": { "type": "string" },
108
+ "files": {
109
+ "type": "array",
110
+ "items": { "type": "string" }
111
+ },
112
+ "related_rules": {
113
+ "type": "array",
114
+ "items": { "type": "string" }
115
+ }
116
+ }
117
+ }
118
+ }
119
+ }
@@ -15,7 +15,7 @@
15
15
  "properties": {
16
16
  "schema_version": {
17
17
  "type": "string",
18
- "const": "1.0.0"
18
+ "oneOf": [{ "const": "1.0.0" }, { "const": "1.1.0" }]
19
19
  },
20
20
  "run_id": {
21
21
  "type": "string",
@@ -38,6 +38,39 @@
38
38
  "phase": {
39
39
  "type": "string",
40
40
  "enum": ["execute", "evaluate", "review"]
41
+ },
42
+ "quality_signal": {
43
+ "type": "integer"
44
+ },
45
+ "bottleneck": {
46
+ "type": "string",
47
+ "enum": [
48
+ "modularity",
49
+ "equality",
50
+ "acyclicity",
51
+ "stability",
52
+ "abstraction"
53
+ ]
54
+ },
55
+ "violation_count": {
56
+ "type": "integer",
57
+ "minimum": 0
58
+ },
59
+ "report_path": {
60
+ "type": "string",
61
+ "description": "Run-relative path to sentrux-report.json"
62
+ },
63
+ "diagnostics_path": {
64
+ "type": "string",
65
+ "description": "Run-relative path to sentrux-diagnostics.json"
66
+ },
67
+ "repair_plan_path": {
68
+ "type": "string",
69
+ "description": "Run-relative path to sentrux-repair-plan.yaml"
70
+ },
71
+ "degraded_reasons": {
72
+ "type": "array",
73
+ "items": { "type": "string" }
41
74
  }
42
75
  }
43
76
  }
@@ -1,22 +1,4 @@
1
- export function packageAgentsPolicyPath(packageRoot: string): string;
2
- export function projectAgentsPolicyPath(projectRoot: string): string;
3
- export function projectPoliciesDir(projectRoot: string): string;
4
-
5
- export interface AgentPolicySpec {
6
- kind: string;
7
- effectiveTools: string[];
8
- extensionsOff: boolean;
9
- /** Subprocess-only: load curated -e extensions instead of full .pi/extensions. */
10
- extensionBundle?: string;
11
- extensionsFull: boolean;
12
- noBuiltinTools: boolean;
13
- readOnly: boolean;
14
- maxTurns?: number;
15
- thinking?: string;
16
- submitTool?: string;
17
- }
18
-
19
- export interface AllowsAgentToolInput {
1
+ export type AllowsAgentToolInput = {
20
2
  packageRoot: string;
21
3
  projectRoot: string;
22
4
  agentId: string;
@@ -24,51 +6,44 @@ export interface AllowsAgentToolInput {
24
6
  toolInput?: Record<string, unknown>;
25
7
  isSubprocess?: boolean;
26
8
  isParentOrchestrator?: boolean;
27
- }
9
+ };
28
10
 
29
- export function loadAgentsPolicyMerged(
11
+ export function allowsAgentTool(input: AllowsAgentToolInput): boolean;
12
+ export function applyAgentPolicyToConfig(
13
+ agent: Record<string, unknown>,
14
+ packageRoot: string,
15
+ projectRoot: string,
16
+ ): Record<string, unknown>;
17
+ export function findProjectRootFromAgentsDir(projectAgentsDir: string): string;
18
+ export function getAgentKind(
30
19
  packageRoot: string,
31
20
  projectRoot: string,
32
- ): {
33
- schemaVersion: string;
34
- kinds: Map<string, unknown>;
35
- agents: Map<string, unknown>;
36
- defaults: unknown;
37
- };
38
-
39
- export function resolveEffectiveTools(
40
21
  agentId: string,
41
- merged: ReturnType<typeof loadAgentsPolicyMerged>,
42
- ): AgentPolicySpec;
43
-
22
+ ): string;
44
23
  export function getAgentPolicySpec(
45
24
  packageRoot: string,
46
25
  projectRoot: string,
47
26
  agentId: string,
48
- ): AgentPolicySpec | null;
49
-
50
- export function getAgentKind(
27
+ ): unknown;
28
+ export function harnessSubagentPhaseHint(
51
29
  packageRoot: string,
52
30
  projectRoot: string,
53
31
  agentId: string,
54
- ): string;
55
-
32
+ ): string | undefined;
33
+ export function isAgtGovernanceActive(projectRoot: string): boolean;
56
34
  export function isHarnessPlanningAgent(agentId: string): boolean;
57
-
58
- export function harnessSubagentPhaseHint(
35
+ export function loadAgentsPolicyMerged(
59
36
  packageRoot: string,
60
37
  projectRoot: string,
38
+ ): unknown;
39
+ export function packageAgentsPolicyPath(packageRoot: string): string;
40
+ export function projectAgentsPolicyPath(projectRoot: string): string;
41
+ export function projectPoliciesDir(projectRoot: string): string;
42
+ export function resolveEffectiveTools(
61
43
  agentId: string,
62
- ): string | null;
63
-
64
- export function allowsAgentTool(input: AllowsAgentToolInput): boolean;
65
-
66
- export function applyAgentPolicyToConfig<T extends { name: string }>(
67
- agent: T,
44
+ merged: unknown,
45
+ ): string[];
46
+ export function resolveExtensionBundlePaths(
68
47
  packageRoot: string,
69
- projectRoot: string,
70
- ): T;
71
-
72
- export function findProjectRootFromAgentsDir(projectAgentsDir: string): string;
73
-
74
- export function isAgtGovernanceActive(projectRoot: string): boolean;
48
+ bundleName: string,
49
+ ): string[];
@@ -262,7 +262,45 @@ function isMutatingBash(command) {
262
262
  command,
263
263
  );
264
264
  }
265
+ function deniesReadOnlyBatchExecute(toolInput) {
266
+ const commands = toolInput.commands;
267
+ if (!Array.isArray(commands)) return false;
268
+ for (const c of commands) {
269
+ const cmd =
270
+ typeof c === "string" ? c : String(c?.command ?? c?.code ?? "");
271
+ if (cmd && isMutatingBash(cmd)) return true;
272
+ }
273
+ return false;
274
+ }
275
+
276
+ function deniesReadOnlyExecute(toolInput) {
277
+ const code = String(toolInput.code ?? toolInput.command ?? "");
278
+ return Boolean(code && isMutatingBash(code));
279
+ }
280
+
281
+ function deniesReadOnlyBash(agentId, toolInput) {
282
+ const command = String(toolInput.command ?? "");
283
+ if (command && isMutatingBash(command)) return true;
284
+ if (
285
+ isHarnessPlanningAgent(agentId) &&
286
+ command &&
287
+ PLANNING_ARTIFACT_JSON_WRITE.test(command)
288
+ ) {
289
+ return true;
290
+ }
291
+ if (
292
+ isHarnessPlanningAgent(agentId) &&
293
+ command &&
294
+ PLANNING_BASH_DENY_PATTERNS.some((p) => p.test(command))
295
+ ) {
296
+ return true;
297
+ }
298
+ return false;
299
+ }
265
300
 
301
+ /**
302
+ * Manifest allowlist + subprocess constraints (replaces harness-subagent-policy.ts).
303
+ */
266
304
  /**
267
305
  * Manifest allowlist + subprocess constraints (replaces harness-subagent-policy.ts).
268
306
  */
@@ -297,40 +335,15 @@ export function allowsAgentTool(input) {
297
335
  if (MUTATING_TOOLS.has(toolName) && spec.readOnly) return false;
298
336
 
299
337
  if (toolName === "ctx_batch_execute" && spec.readOnly) {
300
- const commands = toolInput.commands;
301
- if (Array.isArray(commands)) {
302
- for (const c of commands) {
303
- const cmd =
304
- typeof c === "string"
305
- ? c
306
- : String(c?.command ?? c?.code ?? "");
307
- if (cmd && isMutatingBash(cmd)) return false;
308
- }
309
- }
338
+ if (deniesReadOnlyBatchExecute(toolInput)) return false;
310
339
  }
311
340
 
312
341
  if (toolName === "ctx_execute" && spec.readOnly) {
313
- const code = String(toolInput.code ?? toolInput.command ?? "");
314
- if (code && isMutatingBash(code)) return false;
342
+ if (deniesReadOnlyExecute(toolInput)) return false;
315
343
  }
316
344
 
317
345
  if (toolName === "bash" && spec.readOnly) {
318
- const command = String(toolInput.command ?? "");
319
- if (command && isMutatingBash(command)) return false;
320
- if (
321
- isHarnessPlanningAgent(agentId) &&
322
- command &&
323
- PLANNING_ARTIFACT_JSON_WRITE.test(command)
324
- ) {
325
- return false;
326
- }
327
- if (
328
- isHarnessPlanningAgent(agentId) &&
329
- command &&
330
- PLANNING_BASH_DENY_PATTERNS.some((p) => p.test(command))
331
- ) {
332
- return false;
333
- }
346
+ if (deniesReadOnlyBash(agentId, toolInput)) return false;
334
347
  }
335
348
 
336
349
  return true;