pi-orchestrator-config 4.1.5
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/README.md +808 -0
- package/agents/api-documenter.md +32 -0
- package/agents/bash-expert.md +53 -0
- package/agents/code-reviewer-docs.md +145 -0
- package/agents/code-reviewer-guidelines.md +97 -0
- package/agents/code-reviewer-quality.md +142 -0
- package/agents/code-reviewer-security.md +107 -0
- package/agents/code-reviewer-spec.md +164 -0
- package/agents/debugger.md +63 -0
- package/agents/docker-expert.md +40 -0
- package/agents/docs-fetcher.md +48 -0
- package/agents/git-expert.md +63 -0
- package/agents/github-expert.md +83 -0
- package/agents/go-expert.md +62 -0
- package/agents/issue-reviewer-feasibility.md +60 -0
- package/agents/issue-reviewer-scope.md +63 -0
- package/agents/issue-reviewer-spec.md +77 -0
- package/agents/java-expert.md +31 -0
- package/agents/jenkins-expert.md +38 -0
- package/agents/kubernetes-expert.md +40 -0
- package/agents/planner.md +42 -0
- package/agents/python-expert.md +61 -0
- package/agents/reviewer.md +26 -0
- package/agents/scout.md +40 -0
- package/agents/security-auditor.md +254 -0
- package/agents/technical-documentation-writer.md +37 -0
- package/agents/test-automator.md +55 -0
- package/agents/test-runner.md +47 -0
- package/agents/ts-expert.md +23 -0
- package/agents/worker.md +30 -0
- package/extensions/acpx-provider/index.ts +104 -0
- package/extensions/acpx-provider/load-runtime.ts +99 -0
- package/extensions/acpx-provider/runtime-models.ts +40 -0
- package/extensions/cli-provider/agents/claude.ts +128 -0
- package/extensions/cli-provider/agents/cursor.ts +84 -0
- package/extensions/cli-provider/agents/gemini.ts +100 -0
- package/extensions/cli-provider/discover.ts +77 -0
- package/extensions/cli-provider/index.ts +57 -0
- package/extensions/cli-provider/parsers.ts +304 -0
- package/extensions/cli-provider/providers.ts +58 -0
- package/extensions/cli-provider/runner.ts +225 -0
- package/extensions/cli-provider/runtime-models.ts +32 -0
- package/extensions/cli-provider/session-reaper.ts +122 -0
- package/extensions/cli-provider/sessions.ts +533 -0
- package/extensions/cli-provider/shared/discover-cache.ts +235 -0
- package/extensions/cli-provider/types.ts +48 -0
- package/extensions/coms/coms-net-server.ts +1642 -0
- package/extensions/coms/coms-net-wrapper.ts +521 -0
- package/extensions/coms/coms-net.ts +1589 -0
- package/extensions/coms/coms-p2p.ts +1879 -0
- package/extensions/coms/coms-shared.ts +510 -0
- package/extensions/coms/coms-wrapper.ts +138 -0
- package/extensions/coms/index.ts +20 -0
- package/extensions/coms/themeMap.ts +147 -0
- package/extensions/image-gen/image-gen.ts +332 -0
- package/extensions/image-gen/index.ts +14 -0
- package/extensions/orchestrator/agents.ts +134 -0
- package/extensions/orchestrator/ask-user.ts +293 -0
- package/extensions/orchestrator/async-agents.ts +1037 -0
- package/extensions/orchestrator/async-capability.ts +129 -0
- package/extensions/orchestrator/async-runner.ts +324 -0
- package/extensions/orchestrator/async-status-parse.ts +31 -0
- package/extensions/orchestrator/async-status-ui.ts +201 -0
- package/extensions/orchestrator/async-wait.ts +36 -0
- package/extensions/orchestrator/btw.ts +210 -0
- package/extensions/orchestrator/cron-status-format.ts +126 -0
- package/extensions/orchestrator/cron-status-ui.ts +132 -0
- package/extensions/orchestrator/cron.ts +730 -0
- package/extensions/orchestrator/dreaming.ts +355 -0
- package/extensions/orchestrator/enforcement-helpers.ts +530 -0
- package/extensions/orchestrator/enforcement-rules.ts +329 -0
- package/extensions/orchestrator/enforcement.ts +828 -0
- package/extensions/orchestrator/extended-autocomplete.ts +593 -0
- package/extensions/orchestrator/git-helpers.ts +420 -0
- package/extensions/orchestrator/github-autocomplete.ts +250 -0
- package/extensions/orchestrator/icons.ts +17 -0
- package/extensions/orchestrator/index.ts +100 -0
- package/extensions/orchestrator/memory-embeddings.ts +281 -0
- package/extensions/orchestrator/memory-promotion.ts +449 -0
- package/extensions/orchestrator/memory-provenance.ts +80 -0
- package/extensions/orchestrator/memory-query-class.ts +90 -0
- package/extensions/orchestrator/memory-scoring.ts +434 -0
- package/extensions/orchestrator/memory-tools.ts +883 -0
- package/extensions/orchestrator/memory-tree.ts +212 -0
- package/extensions/orchestrator/nvim.ts +169 -0
- package/extensions/orchestrator/overlay-dashboard-utils.ts +25 -0
- package/extensions/orchestrator/overlay-dashboard.ts +512 -0
- package/extensions/orchestrator/pi-config-review-state.ts +303 -0
- package/extensions/orchestrator/preference-extractor.ts +106 -0
- package/extensions/orchestrator/project-settings.ts +468 -0
- package/extensions/orchestrator/promotion-queue.ts +311 -0
- package/extensions/orchestrator/resolve-agent-model.ts +32 -0
- package/extensions/orchestrator/review-ui.ts +204 -0
- package/extensions/orchestrator/rule-placeholders.ts +12 -0
- package/extensions/orchestrator/rules.ts +481 -0
- package/extensions/orchestrator/session-search.ts +243 -0
- package/extensions/orchestrator/session-validation.ts +374 -0
- package/extensions/orchestrator/situation-report.ts +370 -0
- package/extensions/orchestrator/status-bar.ts +31 -0
- package/extensions/orchestrator/status-line.ts +192 -0
- package/extensions/orchestrator/status.ts +110 -0
- package/extensions/orchestrator/subagent-tool.ts +1748 -0
- package/extensions/orchestrator/task-lifecycle.ts +88 -0
- package/extensions/orchestrator/utils.ts +227 -0
- package/extensions/pidash/index.ts +15 -0
- package/extensions/pidash/pidash.ts +941 -0
- package/extensions/pidiff/index.ts +13 -0
- package/extensions/pidiff/pidiff.ts +369 -0
- package/extensions/providers/acpx-driver.ts +479 -0
- package/extensions/providers/built-in-drivers.ts +52 -0
- package/extensions/providers/claude-driver.ts +319 -0
- package/extensions/providers/cursor-acpx-driver.ts +398 -0
- package/extensions/providers/cursor-cli-driver.ts +484 -0
- package/extensions/providers/gemini-driver.ts +302 -0
- package/extensions/providers/index.ts +513 -0
- package/extensions/shared/build-system-prompt.ts +57 -0
- package/extensions/shared/create-runtime-provider.ts +190 -0
- package/extensions/shared/daemon-manager.ts +248 -0
- package/extensions/shared/file-logger.ts +115 -0
- package/extensions/shared/managed-refresh.ts +211 -0
- package/extensions/shared/provider-driver.ts +295 -0
- package/extensions/shared/provider-errors.ts +305 -0
- package/extensions/shared/provider-registry.ts +388 -0
- package/extensions/shared/stream-builder.ts +275 -0
- package/extensions/shared/types.ts +20 -0
- package/extensions/shared/ui/README.md +10 -0
- package/extensions/shared/ui/badge.tsx +52 -0
- package/extensions/shared/ui/button.tsx +58 -0
- package/extensions/shared/ui/collapsible.tsx +21 -0
- package/extensions/shared/ui/dropdown-menu.tsx +266 -0
- package/extensions/shared/ui/input.tsx +20 -0
- package/extensions/shared/ui/popover.tsx +114 -0
- package/extensions/shared/ui/resizable.tsx +48 -0
- package/extensions/shared/ui/scroll-area.tsx +53 -0
- package/extensions/shared/ui/select.tsx +201 -0
- package/extensions/shared/ui/separator.tsx +25 -0
- package/extensions/shared/ui/tooltip.tsx +64 -0
- package/extensions/shared/ws-client.ts +74 -0
- package/package.json +59 -0
- package/prompts/coderabbit-rate-limit.md +95 -0
- package/prompts/create-coms-feature-manager.md +186 -0
- package/prompts/create-skill.md +128 -0
- package/prompts/domain-model.md +109 -0
- package/prompts/external-ai.md +665 -0
- package/prompts/implement-and-review.md +25 -0
- package/prompts/implement.md +26 -0
- package/prompts/issue-review.md +256 -0
- package/prompts/pr-review.md +753 -0
- package/prompts/query-db.md +154 -0
- package/prompts/refine-review.md +208 -0
- package/prompts/release.md +235 -0
- package/prompts/remember.md +33 -0
- package/prompts/review-handler-status.md +27 -0
- package/prompts/review-handler.md +677 -0
- package/prompts/review-local.md +159 -0
- package/prompts/scout-and-plan.md +21 -0
- package/rules/00-orchestrator-core.md +42 -0
- package/rules/05-issue-first-workflow.md +119 -0
- package/rules/10-agent-routing.md +58 -0
- package/rules/15-mcp-launchpad.md +46 -0
- package/rules/20-code-review-loop.md +234 -0
- package/rules/25-documentation-updates.md +15 -0
- package/rules/30-prompt-templates.md +19 -0
- package/rules/35-memory.md +240 -0
- package/rules/40-critical-rules.md +124 -0
- package/rules/45-file-preview.md +29 -0
- package/rules/50-agent-bug-reporting.md +90 -0
- package/rules/55-coms-protocol.md +63 -0
- package/rules/60-task-tracking.md +66 -0
- package/skills/conflict-resolver/SKILL.md +213 -0
- package/skills/skill-creator/SKILL.md +85 -0
- package/skills/systematic-debugging/SKILL.md +353 -0
- package/skills/test-driven-development/SKILL.md +335 -0
- package/skills/update-agents-md/SKILL.md +282 -0
- package/templates/coms-feature-manager-prompt.md +313 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enforcement rules engine — reads enforced memory entries and provides
|
|
3
|
+
* trigger matching + action execution for the tool_result hook.
|
|
4
|
+
*
|
|
5
|
+
* This module does NOT register hooks — it provides pure functions
|
|
6
|
+
* that enforcement.ts and rules.ts call from their existing hooks.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { execSync } from "node:child_process";
|
|
10
|
+
import {
|
|
11
|
+
loadScores,
|
|
12
|
+
getActiveEntries,
|
|
13
|
+
entryHash,
|
|
14
|
+
type ScoredEntry,
|
|
15
|
+
type EnforcementTrigger,
|
|
16
|
+
type EnforcementAction,
|
|
17
|
+
} from "./memory-scoring.js";
|
|
18
|
+
import { readAllTopicEntries } from "./memory-tree.js";
|
|
19
|
+
|
|
20
|
+
// ── Types ──────────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
export interface EnforcedEntry {
|
|
23
|
+
hash: string;
|
|
24
|
+
text: string;
|
|
25
|
+
trigger: EnforcementTrigger;
|
|
26
|
+
action: EnforcementAction;
|
|
27
|
+
actionCommand?: string;
|
|
28
|
+
verifier?: string;
|
|
29
|
+
entry: ScoredEntry;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Verifier-only entry — has a verifier but trigger/action are optional */
|
|
33
|
+
export interface VerifierEntry {
|
|
34
|
+
hash: string;
|
|
35
|
+
text: string;
|
|
36
|
+
trigger?: EnforcementTrigger;
|
|
37
|
+
verifier: string;
|
|
38
|
+
entry: ScoredEntry;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface TriggerMatch {
|
|
42
|
+
rule: EnforcedEntry;
|
|
43
|
+
matched: string; // what part of the input matched
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface ActionResult {
|
|
47
|
+
output: string;
|
|
48
|
+
success: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ── Loading ────────────────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Load all active memory entries that have enforcement fields.
|
|
55
|
+
* Reads from the existing memory-scores.json — no separate storage.
|
|
56
|
+
*/
|
|
57
|
+
export function loadEnforcedEntries(cwd: string): EnforcedEntry[] {
|
|
58
|
+
// Load ALL entries with trigger+action, not just active/provisional.
|
|
59
|
+
// Enforced rules are code-checked — lifecycle decay should not disable them.
|
|
60
|
+
const scores = loadScores(cwd);
|
|
61
|
+
const all = Object.entries(scores.entries)
|
|
62
|
+
.filter(([, e]) => e.lifecycle !== "dropped" && e.userState !== "forgotten")
|
|
63
|
+
.map(([hash, entry]) => ({ hash, entry }));
|
|
64
|
+
// Build hash→text lookup from topic files
|
|
65
|
+
const topicEntries = readAllTopicEntries(cwd);
|
|
66
|
+
const hashToText = new Map<string, string>();
|
|
67
|
+
for (const te of topicEntries) {
|
|
68
|
+
const line = `- [${te.category}] ${te.text}`;
|
|
69
|
+
hashToText.set(entryHash(line), te.text);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const result: EnforcedEntry[] = [];
|
|
73
|
+
|
|
74
|
+
for (const { hash, entry } of all) {
|
|
75
|
+
if (!entry.trigger || !entry.action) continue;
|
|
76
|
+
|
|
77
|
+
result.push({
|
|
78
|
+
hash,
|
|
79
|
+
text: hashToText.get(hash) || hash,
|
|
80
|
+
trigger: entry.trigger,
|
|
81
|
+
action: entry.action,
|
|
82
|
+
actionCommand: entry.actionCommand,
|
|
83
|
+
verifier: entry.verifier,
|
|
84
|
+
entry,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Load entries that have semantic verifiers (for turn_end checking).
|
|
93
|
+
* Reads active entries with a `verifier` field directly, without requiring
|
|
94
|
+
* `trigger` and `action` (which loadEnforcedEntries demands).
|
|
95
|
+
*/
|
|
96
|
+
export function loadVerifierEntries(cwd: string): VerifierEntry[] {
|
|
97
|
+
// Same as loadEnforcedEntries — enforced rules bypass lifecycle decay
|
|
98
|
+
const scores = loadScores(cwd);
|
|
99
|
+
const all = Object.entries(scores.entries)
|
|
100
|
+
.filter(([, e]) => e.lifecycle !== "dropped" && e.userState !== "forgotten")
|
|
101
|
+
.map(([hash, entry]) => ({ hash, entry }));
|
|
102
|
+
const topicEntries = readAllTopicEntries(cwd);
|
|
103
|
+
const hashToText = new Map<string, string>();
|
|
104
|
+
for (const te of topicEntries) {
|
|
105
|
+
const line = `- [${te.category}] ${te.text}`;
|
|
106
|
+
hashToText.set(entryHash(line), te.text);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const result: VerifierEntry[] = [];
|
|
110
|
+
|
|
111
|
+
for (const { hash, entry } of all) {
|
|
112
|
+
if (!entry.verifier) continue;
|
|
113
|
+
|
|
114
|
+
result.push({
|
|
115
|
+
hash,
|
|
116
|
+
text: hashToText.get(hash) || hash,
|
|
117
|
+
trigger: entry.trigger,
|
|
118
|
+
verifier: entry.verifier,
|
|
119
|
+
entry,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ── Trigger Matching ───────────────────────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Check if a bash command matches a trigger.
|
|
130
|
+
*/
|
|
131
|
+
function matchBashTrigger(
|
|
132
|
+
trigger: EnforcementTrigger,
|
|
133
|
+
command: string,
|
|
134
|
+
): string | null {
|
|
135
|
+
if (trigger.startsWith("bash_contains ")) {
|
|
136
|
+
const needle = trigger.slice("bash_contains ".length);
|
|
137
|
+
if (command.includes(needle)) return needle;
|
|
138
|
+
}
|
|
139
|
+
if (trigger.startsWith("bash_regex ")) {
|
|
140
|
+
const pattern = trigger.slice("bash_regex ".length);
|
|
141
|
+
// Guard against ReDoS: limit pattern length
|
|
142
|
+
if (pattern.length > 200) return null;
|
|
143
|
+
try {
|
|
144
|
+
const re = new RegExp(pattern);
|
|
145
|
+
// Use a bounded match — limit input to first 4000 chars
|
|
146
|
+
const m = command.slice(0, 4000).match(re);
|
|
147
|
+
if (m) return m[0];
|
|
148
|
+
} catch {
|
|
149
|
+
// Invalid regex — skip
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Check if a tool name matches a trigger.
|
|
157
|
+
*/
|
|
158
|
+
function matchToolTrigger(
|
|
159
|
+
trigger: EnforcementTrigger,
|
|
160
|
+
toolName: string,
|
|
161
|
+
): string | null {
|
|
162
|
+
if (trigger.startsWith("tool_name ")) {
|
|
163
|
+
const expected = trigger.slice("tool_name ".length);
|
|
164
|
+
if (toolName === expected) return toolName;
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Check if a file path matches a trigger.
|
|
171
|
+
*/
|
|
172
|
+
function matchFileTrigger(
|
|
173
|
+
trigger: EnforcementTrigger,
|
|
174
|
+
filePath: string,
|
|
175
|
+
): string | null {
|
|
176
|
+
if (trigger.startsWith("file_modified ")) {
|
|
177
|
+
const glob = trigger.slice("file_modified ".length);
|
|
178
|
+
// Simple glob: *.py matches .py extension, exact match otherwise
|
|
179
|
+
if (glob.startsWith("*.")) {
|
|
180
|
+
const ext = glob.slice(1); // ".py"
|
|
181
|
+
if (filePath.endsWith(ext)) return filePath;
|
|
182
|
+
} else if (filePath.includes(glob)) {
|
|
183
|
+
return filePath;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Find all enforced entries whose triggers match a bash command.
|
|
191
|
+
*/
|
|
192
|
+
export function matchBashCommand(
|
|
193
|
+
entries: EnforcedEntry[],
|
|
194
|
+
command: string,
|
|
195
|
+
): TriggerMatch[] {
|
|
196
|
+
const matches: TriggerMatch[] = [];
|
|
197
|
+
for (const rule of entries) {
|
|
198
|
+
const hit = matchBashTrigger(rule.trigger, command);
|
|
199
|
+
if (hit) matches.push({ rule, matched: hit });
|
|
200
|
+
}
|
|
201
|
+
return matches;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Find all enforced entries whose triggers match a tool call.
|
|
206
|
+
*/
|
|
207
|
+
export function matchToolCall(
|
|
208
|
+
entries: EnforcedEntry[],
|
|
209
|
+
toolName: string,
|
|
210
|
+
input: Record<string, any>,
|
|
211
|
+
): TriggerMatch[] {
|
|
212
|
+
const matches: TriggerMatch[] = [];
|
|
213
|
+
for (const rule of entries) {
|
|
214
|
+
// Check tool_name triggers
|
|
215
|
+
const toolHit = matchToolTrigger(rule.trigger, toolName);
|
|
216
|
+
if (toolHit) {
|
|
217
|
+
matches.push({ rule, matched: toolHit });
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Check bash_contains/bash_regex for bash tool
|
|
222
|
+
if (toolName === "bash" && input?.command) {
|
|
223
|
+
const bashHit = matchBashTrigger(rule.trigger, input.command);
|
|
224
|
+
if (bashHit) {
|
|
225
|
+
matches.push({ rule, matched: bashHit });
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Check file_modified for write/edit tools
|
|
231
|
+
if ((toolName === "write" || toolName === "edit") && input?.path) {
|
|
232
|
+
const fileHit = matchFileTrigger(rule.trigger, input.path);
|
|
233
|
+
if (fileHit) {
|
|
234
|
+
matches.push({ rule, matched: fileHit });
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Subagent matching is handled in the tool_result hook via structured
|
|
240
|
+
// tool call extraction from subagent messages (not task text matching).
|
|
241
|
+
}
|
|
242
|
+
return matches;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Check verifier rules against a turn's tool results.
|
|
247
|
+
* Returns list of violated verifier strings.
|
|
248
|
+
*/
|
|
249
|
+
export function checkVerifiers(
|
|
250
|
+
verifierEntries: VerifierEntry[],
|
|
251
|
+
toolResults: Array<{ toolName: string; input: Record<string, any> }>,
|
|
252
|
+
): string[] {
|
|
253
|
+
const violations: string[] = [];
|
|
254
|
+
|
|
255
|
+
for (const rule of verifierEntries) {
|
|
256
|
+
const m = rule.verifier.match(/^tool_called (\S+) before (.+)$/);
|
|
257
|
+
if (!m) continue;
|
|
258
|
+
|
|
259
|
+
const requiredTool = m[1];
|
|
260
|
+
const beforeCommand = m[2];
|
|
261
|
+
|
|
262
|
+
let requiredIdx = -1;
|
|
263
|
+
let triggerIdx = -1;
|
|
264
|
+
for (let i = 0; i < toolResults.length; i++) {
|
|
265
|
+
const trName = toolResults[i].toolName;
|
|
266
|
+
const trInput = toolResults[i].input || {};
|
|
267
|
+
if (trName === requiredTool && requiredIdx === -1) requiredIdx = i;
|
|
268
|
+
// Match beforeCommand in bash commands — this is the triggering event
|
|
269
|
+
// that the verifier checks (e.g., "gh pr merge" in a bash call)
|
|
270
|
+
if (triggerIdx === -1 && trName === "bash" && trInput?.command?.includes(beforeCommand)) {
|
|
271
|
+
triggerIdx = i;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (triggerIdx >= 0 && (requiredIdx < 0 || requiredIdx >= triggerIdx)) {
|
|
276
|
+
violations.push(rule.verifier);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return violations;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Execute a run_after action command.
|
|
285
|
+
*/
|
|
286
|
+
export function executeAction(
|
|
287
|
+
command: string,
|
|
288
|
+
cwd: string,
|
|
289
|
+
): ActionResult {
|
|
290
|
+
// Optional allowlist — if PI_ENFORCEMENT_ALLOWED_COMMANDS is set,
|
|
291
|
+
// only permit exact-match commands (colon-separated)
|
|
292
|
+
const allowedEnv = process.env.PI_ENFORCEMENT_ALLOWED_COMMANDS;
|
|
293
|
+
if (allowedEnv) {
|
|
294
|
+
const allowlist = allowedEnv.split(":").map(s => s.trim()).filter(Boolean);
|
|
295
|
+
// Exact match only — no prefix matching to prevent shell chaining bypass
|
|
296
|
+
// Both sides trimmed to avoid whitespace mismatches from storage
|
|
297
|
+
const commandTrimmed = command.trim();
|
|
298
|
+
const permitted = allowlist.some(allowed => commandTrimmed === allowed);
|
|
299
|
+
if (!permitted) {
|
|
300
|
+
return { output: `Blocked: command not in PI_ENFORCEMENT_ALLOWED_COMMANDS allowlist`, success: false };
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Safety: reject commands that match dangerous patterns
|
|
305
|
+
const BLOCKED_PATTERNS = [
|
|
306
|
+
/\bcurl\b.*\|\s*(?:bash|sh|zsh)\b/i, // curl | bash
|
|
307
|
+
/\bwget\b.*\|\s*(?:bash|sh|zsh)\b/i, // wget | bash
|
|
308
|
+
/\brm\s+(-[rf]+\s+)*\//, // rm -rf /
|
|
309
|
+
/\bsudo\b/, // sudo
|
|
310
|
+
/\bchmod\s+[0-7]*7[0-7]*\b/, // world-writable chmod
|
|
311
|
+
];
|
|
312
|
+
for (const pattern of BLOCKED_PATTERNS) {
|
|
313
|
+
if (pattern.test(command)) {
|
|
314
|
+
return { output: `Blocked: enforcement command matches dangerous pattern: ${pattern}`, success: false };
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
try {
|
|
318
|
+
const output = execSync(command, {
|
|
319
|
+
cwd,
|
|
320
|
+
timeout: 60_000,
|
|
321
|
+
encoding: "utf-8",
|
|
322
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
323
|
+
});
|
|
324
|
+
return { output: output.trim(), success: true };
|
|
325
|
+
} catch (e: any) {
|
|
326
|
+
const stderr = e.stderr || e.message || "Unknown error";
|
|
327
|
+
return { output: stderr.trim(), success: false };
|
|
328
|
+
}
|
|
329
|
+
}
|