omegon 0.6.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/.gitattributes +3 -0
- package/AGENTS.md +16 -0
- package/LICENSE +15 -0
- package/README.md +289 -0
- package/bin/pi.mjs +30 -0
- package/extensions/00-secrets/index.ts +1126 -0
- package/extensions/01-auth/auth.ts +401 -0
- package/extensions/01-auth/index.ts +289 -0
- package/extensions/auto-compact.ts +42 -0
- package/extensions/bootstrap/deps.ts +291 -0
- package/extensions/bootstrap/index.ts +811 -0
- package/extensions/chronos/chronos.sh +487 -0
- package/extensions/chronos/index.ts +148 -0
- package/extensions/cleave/assessment.ts +754 -0
- package/extensions/cleave/bridge.ts +31 -0
- package/extensions/cleave/conflicts.ts +250 -0
- package/extensions/cleave/dispatcher.ts +808 -0
- package/extensions/cleave/guardrails.ts +426 -0
- package/extensions/cleave/index.ts +3121 -0
- package/extensions/cleave/lifecycle-emitter.ts +20 -0
- package/extensions/cleave/openspec.ts +811 -0
- package/extensions/cleave/planner.ts +260 -0
- package/extensions/cleave/review.ts +579 -0
- package/extensions/cleave/skills.ts +355 -0
- package/extensions/cleave/types.ts +261 -0
- package/extensions/cleave/workspace.ts +861 -0
- package/extensions/cleave/worktree.ts +243 -0
- package/extensions/core-renderers.ts +253 -0
- package/extensions/dashboard/context-gauge.ts +58 -0
- package/extensions/dashboard/file-watch.ts +14 -0
- package/extensions/dashboard/footer.ts +1145 -0
- package/extensions/dashboard/git.ts +185 -0
- package/extensions/dashboard/index.ts +478 -0
- package/extensions/dashboard/memory-audit.ts +34 -0
- package/extensions/dashboard/overlay-data.ts +705 -0
- package/extensions/dashboard/overlay.ts +365 -0
- package/extensions/dashboard/render-utils.ts +54 -0
- package/extensions/dashboard/types.ts +191 -0
- package/extensions/dashboard/uri-helper.ts +45 -0
- package/extensions/debug.ts +69 -0
- package/extensions/defaults.ts +282 -0
- package/extensions/design-tree/dashboard-state.ts +161 -0
- package/extensions/design-tree/design-card.ts +362 -0
- package/extensions/design-tree/index.ts +2130 -0
- package/extensions/design-tree/lifecycle-emitter.ts +41 -0
- package/extensions/design-tree/tree.ts +1607 -0
- package/extensions/design-tree/types.ts +163 -0
- package/extensions/distill.ts +127 -0
- package/extensions/effort/index.ts +395 -0
- package/extensions/effort/tiers.ts +146 -0
- package/extensions/effort/types.ts +105 -0
- package/extensions/lib/git-state.ts +227 -0
- package/extensions/lib/local-models.ts +157 -0
- package/extensions/lib/model-preferences.ts +51 -0
- package/extensions/lib/model-routing.ts +720 -0
- package/extensions/lib/operator-fallback.ts +205 -0
- package/extensions/lib/operator-profile.ts +360 -0
- package/extensions/lib/slash-command-bridge.ts +253 -0
- package/extensions/lib/typebox-helpers.ts +16 -0
- package/extensions/local-inference/index.ts +727 -0
- package/extensions/mcp-bridge/README.md +220 -0
- package/extensions/mcp-bridge/index.ts +951 -0
- package/extensions/mcp-bridge/lib.ts +365 -0
- package/extensions/mcp-bridge/mcp.json +3 -0
- package/extensions/mcp-bridge/package.json +11 -0
- package/extensions/model-budget.ts +752 -0
- package/extensions/offline-driver.ts +403 -0
- package/extensions/openspec/archive-gate.ts +164 -0
- package/extensions/openspec/branch-cleanup.ts +64 -0
- package/extensions/openspec/dashboard-state.ts +50 -0
- package/extensions/openspec/index.ts +1917 -0
- package/extensions/openspec/lifecycle-emitter.ts +65 -0
- package/extensions/openspec/lifecycle-files.ts +70 -0
- package/extensions/openspec/lifecycle.ts +50 -0
- package/extensions/openspec/reconcile.ts +187 -0
- package/extensions/openspec/spec.ts +1385 -0
- package/extensions/openspec/types.ts +98 -0
- package/extensions/project-memory/DESIGN-global-mind.md +198 -0
- package/extensions/project-memory/README.md +202 -0
- package/extensions/project-memory/api-types.ts +382 -0
- package/extensions/project-memory/compaction-policy.ts +29 -0
- package/extensions/project-memory/core.ts +164 -0
- package/extensions/project-memory/embeddings.ts +230 -0
- package/extensions/project-memory/extraction-v2.ts +861 -0
- package/extensions/project-memory/factstore.ts +2177 -0
- package/extensions/project-memory/index.ts +3459 -0
- package/extensions/project-memory/injection-metrics.ts +91 -0
- package/extensions/project-memory/jsonl-io.ts +12 -0
- package/extensions/project-memory/lifecycle.ts +331 -0
- package/extensions/project-memory/migration.ts +293 -0
- package/extensions/project-memory/package.json +9 -0
- package/extensions/project-memory/sci-renderers.ts +7 -0
- package/extensions/project-memory/template.ts +103 -0
- package/extensions/project-memory/triggers.ts +52 -0
- package/extensions/project-memory/types.ts +102 -0
- package/extensions/render/composition/fonts/Inter-Bold.ttf +0 -0
- package/extensions/render/composition/fonts/Inter-Regular.ttf +0 -0
- package/extensions/render/composition/fonts/Tomorrow-Bold.ttf +0 -0
- package/extensions/render/composition/fonts/Tomorrow-Regular.ttf +0 -0
- package/extensions/render/composition/package-lock.json +534 -0
- package/extensions/render/composition/package.json +22 -0
- package/extensions/render/composition/render.mjs +246 -0
- package/extensions/render/composition/test-comp.tsx +87 -0
- package/extensions/render/composition/types.ts +24 -0
- package/extensions/render/excalidraw/UPSTREAM.md +81 -0
- package/extensions/render/excalidraw/elements.ts +764 -0
- package/extensions/render/excalidraw/index.ts +66 -0
- package/extensions/render/excalidraw/types.ts +223 -0
- package/extensions/render/excalidraw-renderer/pyproject.toml +8 -0
- package/extensions/render/excalidraw-renderer/render_excalidraw.py +182 -0
- package/extensions/render/excalidraw-renderer/render_template.html +59 -0
- package/extensions/render/index.ts +830 -0
- package/extensions/render/native-diagrams/index.ts +57 -0
- package/extensions/render/native-diagrams/motifs.ts +542 -0
- package/extensions/render/native-diagrams/raster.ts +8 -0
- package/extensions/render/native-diagrams/scene.ts +75 -0
- package/extensions/render/native-diagrams/spec.ts +204 -0
- package/extensions/render/native-diagrams/svg.ts +116 -0
- package/extensions/sci-ui.ts +304 -0
- package/extensions/session-log.ts +174 -0
- package/extensions/shared-state.ts +146 -0
- package/extensions/spinner-verbs.ts +91 -0
- package/extensions/style.ts +281 -0
- package/extensions/terminal-title.ts +191 -0
- package/extensions/tool-profile/index.ts +291 -0
- package/extensions/tool-profile/profiles.ts +290 -0
- package/extensions/types.d.ts +9 -0
- package/extensions/vault/index.ts +185 -0
- package/extensions/version-check.ts +90 -0
- package/extensions/view/index.ts +859 -0
- package/extensions/view/uri-resolver.ts +148 -0
- package/extensions/web-search/index.ts +182 -0
- package/extensions/web-search/providers.ts +121 -0
- package/extensions/web-ui/index.ts +110 -0
- package/extensions/web-ui/server.ts +265 -0
- package/extensions/web-ui/state.ts +462 -0
- package/extensions/web-ui/static/index.html +145 -0
- package/extensions/web-ui/types.ts +284 -0
- package/package.json +76 -0
- package/prompts/init.md +75 -0
- package/prompts/new-repo.md +54 -0
- package/prompts/oci-login.md +56 -0
- package/prompts/status.md +50 -0
- package/settings.json +4 -0
- package/skills/cleave/SKILL.md +218 -0
- package/skills/git/SKILL.md +209 -0
- package/skills/git/_reference/ci-validation.md +204 -0
- package/skills/oci/SKILL.md +338 -0
- package/skills/openspec/SKILL.md +346 -0
- package/skills/pi-extensions/SKILL.md +191 -0
- package/skills/pi-tui/SKILL.md +517 -0
- package/skills/python/SKILL.md +189 -0
- package/skills/rust/SKILL.md +268 -0
- package/skills/security/SKILL.md +206 -0
- package/skills/style/SKILL.md +264 -0
- package/skills/typescript/SKILL.md +225 -0
- package/skills/vault/SKILL.md +102 -0
- package/themes/alpharius-legacy.json +85 -0
- package/themes/alpharius.conf +59 -0
- package/themes/alpharius.json +88 -0
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cleave/guardrails — Deterministic guardrail discovery, execution, and formatting.
|
|
3
|
+
*
|
|
4
|
+
* Discovers checks from package.json scripts, auto-detection of build tools,
|
|
5
|
+
* and skill frontmatter. Runs them and formats results for injection into
|
|
6
|
+
* review loops, post-merge checks, and /assess commands.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { execSync } from "node:child_process";
|
|
10
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
|
|
13
|
+
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
export interface GuardrailCheck {
|
|
16
|
+
name: string;
|
|
17
|
+
cmd: string;
|
|
18
|
+
timeout: number;
|
|
19
|
+
source: "package-script" | "skill-frontmatter" | "auto-detect";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface GuardrailResult {
|
|
23
|
+
check: GuardrailCheck;
|
|
24
|
+
passed: boolean;
|
|
25
|
+
exitCode: number;
|
|
26
|
+
output: string;
|
|
27
|
+
durationMs: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface GuardrailSuite {
|
|
31
|
+
results: GuardrailResult[];
|
|
32
|
+
allPassed: boolean;
|
|
33
|
+
durationMs: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface SkillGuardrailEntry {
|
|
37
|
+
name: string;
|
|
38
|
+
cmd: string;
|
|
39
|
+
timeout?: number;
|
|
40
|
+
condition?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ─── Frontmatter Parsing ─────────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Parse YAML frontmatter from a SKILL.md file, extracting the `guardrails:` array.
|
|
47
|
+
* Uses simple line-based parsing — no yaml dependency.
|
|
48
|
+
*/
|
|
49
|
+
export function parseSkillFrontmatter(content: string): {
|
|
50
|
+
guardrails?: SkillGuardrailEntry[];
|
|
51
|
+
} {
|
|
52
|
+
const lines = content.split("\n");
|
|
53
|
+
if (lines[0]?.trim() !== "---") return {};
|
|
54
|
+
|
|
55
|
+
let endIdx = -1;
|
|
56
|
+
for (let i = 1; i < lines.length; i++) {
|
|
57
|
+
if (lines[i]?.trim() === "---") {
|
|
58
|
+
endIdx = i;
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (endIdx < 0) return {};
|
|
63
|
+
|
|
64
|
+
const fmLines = lines.slice(1, endIdx);
|
|
65
|
+
|
|
66
|
+
// Find guardrails: key
|
|
67
|
+
let guardrailStart = -1;
|
|
68
|
+
for (let i = 0; i < fmLines.length; i++) {
|
|
69
|
+
if (fmLines[i]?.match(/^guardrails:\s*$/)) {
|
|
70
|
+
guardrailStart = i + 1;
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (guardrailStart < 0) return {};
|
|
75
|
+
|
|
76
|
+
const entries: SkillGuardrailEntry[] = [];
|
|
77
|
+
let current: Partial<SkillGuardrailEntry> | null = null;
|
|
78
|
+
|
|
79
|
+
for (let i = guardrailStart; i < fmLines.length; i++) {
|
|
80
|
+
const line = fmLines[i] ?? "";
|
|
81
|
+
// Stop if we hit a top-level key (no leading whitespace, has colon)
|
|
82
|
+
if (line.match(/^\S/) && line.includes(":")) break;
|
|
83
|
+
|
|
84
|
+
const itemMatch = line.match(/^\s*-\s+(\w+):\s*(.+)$/);
|
|
85
|
+
if (itemMatch) {
|
|
86
|
+
// New array item starting with first property
|
|
87
|
+
if (current?.name && current?.cmd) {
|
|
88
|
+
entries.push(current as SkillGuardrailEntry);
|
|
89
|
+
}
|
|
90
|
+
current = {};
|
|
91
|
+
const key = itemMatch[1] as string;
|
|
92
|
+
const val = itemMatch[2] as string;
|
|
93
|
+
assignEntry(current, key, val);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const propMatch = line.match(/^\s+(\w+):\s*(.+)$/);
|
|
98
|
+
if (propMatch && current) {
|
|
99
|
+
const key = propMatch[1] as string;
|
|
100
|
+
const val = propMatch[2] as string;
|
|
101
|
+
assignEntry(current, key, val);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (current?.name && current?.cmd) {
|
|
106
|
+
entries.push(current as SkillGuardrailEntry);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return entries.length > 0 ? { guardrails: entries } : {};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function assignEntry(
|
|
113
|
+
entry: Partial<SkillGuardrailEntry>,
|
|
114
|
+
key: string,
|
|
115
|
+
val: string
|
|
116
|
+
): void {
|
|
117
|
+
switch (key) {
|
|
118
|
+
case "name":
|
|
119
|
+
entry.name = val.trim();
|
|
120
|
+
break;
|
|
121
|
+
case "cmd":
|
|
122
|
+
entry.cmd = val.trim();
|
|
123
|
+
break;
|
|
124
|
+
case "timeout":
|
|
125
|
+
entry.timeout = parseInt(val.trim(), 10) || undefined;
|
|
126
|
+
break;
|
|
127
|
+
case "condition":
|
|
128
|
+
entry.condition = val.trim();
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ─── Condition Evaluation ────────────────────────────────────────────────────
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Evaluate a condition string. Currently supports:
|
|
137
|
+
* - `file_exists(path)` — checks if the file exists relative to cwd
|
|
138
|
+
*/
|
|
139
|
+
export function evaluateCondition(condition: string, cwd: string): boolean {
|
|
140
|
+
const match = condition.match(/^file_exists\((.+)\)$/);
|
|
141
|
+
if (match) {
|
|
142
|
+
const filePath = match[1] as string;
|
|
143
|
+
return existsSync(join(cwd, filePath));
|
|
144
|
+
}
|
|
145
|
+
// Unknown condition — default to true (include the check)
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ─── Project Skill Resolution ────────────────────────────────────────────────
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Quick scan: which skills are relevant to this project based on files present?
|
|
153
|
+
* Returns skill names (not full paths — use resolveSkillPath from skills.ts for that).
|
|
154
|
+
*/
|
|
155
|
+
function detectProjectSkills(cwd: string): string[] {
|
|
156
|
+
const skills: string[] = [];
|
|
157
|
+
if (existsSync(join(cwd, "tsconfig.json")) || existsSync(join(cwd, "package.json"))) {
|
|
158
|
+
skills.push("typescript");
|
|
159
|
+
}
|
|
160
|
+
if (existsSync(join(cwd, "pyproject.toml")) || existsSync(join(cwd, "setup.py"))) {
|
|
161
|
+
skills.push("python");
|
|
162
|
+
}
|
|
163
|
+
if (existsSync(join(cwd, "Cargo.toml"))) {
|
|
164
|
+
skills.push("rust");
|
|
165
|
+
}
|
|
166
|
+
return skills;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Resolve project-relevant skill SKILL.md paths for guardrail frontmatter parsing.
|
|
171
|
+
* Uses the same search logic as skills.ts resolveSkillPath.
|
|
172
|
+
*/
|
|
173
|
+
export function resolveProjectSkillPaths(cwd: string): string[] {
|
|
174
|
+
const skillNames = detectProjectSkills(cwd);
|
|
175
|
+
const paths: string[] = [];
|
|
176
|
+
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
177
|
+
|
|
178
|
+
for (const name of skillNames) {
|
|
179
|
+
// Search well-known skill directories (mirrors skills.ts getSkillSearchPaths)
|
|
180
|
+
const candidates = [
|
|
181
|
+
join(cwd, "skills", name, "SKILL.md"),
|
|
182
|
+
...(home ? [
|
|
183
|
+
join(home, ".pi", "agent", "skills", name, "SKILL.md"),
|
|
184
|
+
] : []),
|
|
185
|
+
];
|
|
186
|
+
for (const c of candidates) {
|
|
187
|
+
if (existsSync(c)) {
|
|
188
|
+
paths.push(c);
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return paths;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ─── Discovery ───────────────────────────────────────────────────────────────
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Discover guardrail checks from package.json, auto-detection, and skill frontmatter.
|
|
200
|
+
* Priority: package-script > auto-detect > skill-frontmatter (dedup by name).
|
|
201
|
+
*
|
|
202
|
+
* If skillPaths is not provided, automatically resolves project-relevant skills.
|
|
203
|
+
*/
|
|
204
|
+
export function discoverGuardrails(
|
|
205
|
+
cwd: string,
|
|
206
|
+
skillPaths?: string[]
|
|
207
|
+
): GuardrailCheck[] {
|
|
208
|
+
const checks: GuardrailCheck[] = [];
|
|
209
|
+
const seen = new Map<string, GuardrailCheck["source"]>();
|
|
210
|
+
|
|
211
|
+
function addCheck(check: GuardrailCheck): void {
|
|
212
|
+
const existing = seen.get(check.name);
|
|
213
|
+
if (existing) {
|
|
214
|
+
// Priority: package-script > auto-detect > skill-frontmatter
|
|
215
|
+
const priority: Record<GuardrailCheck["source"], number> = {
|
|
216
|
+
"package-script": 3,
|
|
217
|
+
"auto-detect": 2,
|
|
218
|
+
"skill-frontmatter": 1,
|
|
219
|
+
};
|
|
220
|
+
if (priority[check.source] <= priority[existing]) return;
|
|
221
|
+
// Replace lower-priority entry
|
|
222
|
+
const idx = checks.findIndex((c) => c.name === check.name);
|
|
223
|
+
if (idx >= 0) checks[idx] = check;
|
|
224
|
+
seen.set(check.name, check.source);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
checks.push(check);
|
|
228
|
+
seen.set(check.name, check.source);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// 1. package.json scripts
|
|
232
|
+
const pkgPath = join(cwd, "package.json");
|
|
233
|
+
if (existsSync(pkgPath)) {
|
|
234
|
+
try {
|
|
235
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8")) as {
|
|
236
|
+
scripts?: Record<string, string>;
|
|
237
|
+
};
|
|
238
|
+
const scripts = pkg.scripts ?? {};
|
|
239
|
+
if (scripts.typecheck) {
|
|
240
|
+
addCheck({
|
|
241
|
+
name: "typecheck",
|
|
242
|
+
cmd: scripts.typecheck,
|
|
243
|
+
timeout: 30,
|
|
244
|
+
source: "package-script",
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
if (scripts.lint) {
|
|
248
|
+
addCheck({
|
|
249
|
+
name: "lint",
|
|
250
|
+
cmd: scripts.lint,
|
|
251
|
+
timeout: 30,
|
|
252
|
+
source: "package-script",
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
} catch {
|
|
256
|
+
// Malformed package.json — skip
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// 2. Auto-detection
|
|
261
|
+
if (!seen.has("typecheck") && existsSync(join(cwd, "tsconfig.json"))) {
|
|
262
|
+
addCheck({
|
|
263
|
+
name: "typecheck",
|
|
264
|
+
cmd: "npx tsc --noEmit",
|
|
265
|
+
timeout: 30,
|
|
266
|
+
source: "auto-detect",
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
if (existsSync(join(cwd, "pyproject.toml"))) {
|
|
270
|
+
addCheck({
|
|
271
|
+
name: "typecheck-python",
|
|
272
|
+
cmd: "mypy .",
|
|
273
|
+
timeout: 30,
|
|
274
|
+
source: "auto-detect",
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
if (existsSync(join(cwd, "Cargo.toml"))) {
|
|
278
|
+
addCheck({
|
|
279
|
+
name: "clippy",
|
|
280
|
+
cmd: "cargo clippy -- -D warnings",
|
|
281
|
+
timeout: 60,
|
|
282
|
+
source: "auto-detect",
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// 3. Skill frontmatter — auto-resolve if not provided
|
|
287
|
+
const effectiveSkillPaths = skillPaths ?? resolveProjectSkillPaths(cwd);
|
|
288
|
+
if (effectiveSkillPaths.length > 0) {
|
|
289
|
+
for (const sp of effectiveSkillPaths) {
|
|
290
|
+
if (!existsSync(sp)) continue;
|
|
291
|
+
try {
|
|
292
|
+
const content = readFileSync(sp, "utf-8");
|
|
293
|
+
const fm = parseSkillFrontmatter(content);
|
|
294
|
+
if (fm.guardrails) {
|
|
295
|
+
for (const entry of fm.guardrails) {
|
|
296
|
+
// Evaluate condition
|
|
297
|
+
if (entry.condition && !evaluateCondition(entry.condition, cwd)) {
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
addCheck({
|
|
301
|
+
name: entry.name,
|
|
302
|
+
cmd: entry.cmd,
|
|
303
|
+
timeout: entry.timeout ?? 30,
|
|
304
|
+
source: "skill-frontmatter",
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
} catch {
|
|
309
|
+
// Unreadable skill file — skip
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return checks;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ─── Execution ───────────────────────────────────────────────────────────────
|
|
318
|
+
|
|
319
|
+
const MAX_OUTPUT_LINES = 50;
|
|
320
|
+
|
|
321
|
+
function capOutput(output: string): string {
|
|
322
|
+
const lines = output.split("\n");
|
|
323
|
+
if (lines.length <= MAX_OUTPUT_LINES) return output;
|
|
324
|
+
return (
|
|
325
|
+
lines.slice(0, MAX_OUTPUT_LINES).join("\n") +
|
|
326
|
+
`\n... (${lines.length - MAX_OUTPUT_LINES} more lines truncated)`
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Run all guardrail checks sequentially and collect results.
|
|
332
|
+
*/
|
|
333
|
+
export function runGuardrails(
|
|
334
|
+
cwd: string,
|
|
335
|
+
checks: GuardrailCheck[]
|
|
336
|
+
): GuardrailSuite {
|
|
337
|
+
const suiteStart = Date.now();
|
|
338
|
+
const results: GuardrailResult[] = [];
|
|
339
|
+
|
|
340
|
+
for (const check of checks) {
|
|
341
|
+
const start = Date.now();
|
|
342
|
+
let passed = false;
|
|
343
|
+
let exitCode = 1;
|
|
344
|
+
let output = "";
|
|
345
|
+
|
|
346
|
+
try {
|
|
347
|
+
const result = execSync(check.cmd, {
|
|
348
|
+
cwd,
|
|
349
|
+
timeout: check.timeout * 1000,
|
|
350
|
+
encoding: "utf-8",
|
|
351
|
+
stdio: "pipe",
|
|
352
|
+
});
|
|
353
|
+
output = capOutput(result ?? "");
|
|
354
|
+
passed = true;
|
|
355
|
+
exitCode = 0;
|
|
356
|
+
} catch (err: unknown) {
|
|
357
|
+
const e = err as {
|
|
358
|
+
status?: number;
|
|
359
|
+
stdout?: string;
|
|
360
|
+
stderr?: string;
|
|
361
|
+
killed?: boolean;
|
|
362
|
+
message?: string;
|
|
363
|
+
};
|
|
364
|
+
exitCode = e.status ?? 1;
|
|
365
|
+
const parts: string[] = [];
|
|
366
|
+
if (e.stdout) parts.push(e.stdout);
|
|
367
|
+
if (e.stderr) parts.push(e.stderr);
|
|
368
|
+
output = capOutput(
|
|
369
|
+
parts.length > 0 ? parts.join("\n") : e.message ?? "Unknown error"
|
|
370
|
+
);
|
|
371
|
+
if (e.killed) {
|
|
372
|
+
output = `[TIMEOUT after ${check.timeout}s]\n${output}`;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
results.push({
|
|
377
|
+
check,
|
|
378
|
+
passed,
|
|
379
|
+
exitCode,
|
|
380
|
+
output,
|
|
381
|
+
durationMs: Date.now() - start,
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return {
|
|
386
|
+
results,
|
|
387
|
+
allPassed: results.every((r) => r.passed),
|
|
388
|
+
durationMs: Date.now() - suiteStart,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// ─── Formatting ──────────────────────────────────────────────────────────────
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Format guardrail results as markdown for injection into prompts / reports.
|
|
396
|
+
*/
|
|
397
|
+
export function formatGuardrailResults(suite: GuardrailSuite): string {
|
|
398
|
+
if (suite.allPassed) {
|
|
399
|
+
const names = suite.results.map((r) => r.check.name).join(", ");
|
|
400
|
+
return `✅ All deterministic checks passed (${names})`;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const failures = suite.results.filter((r) => !r.passed);
|
|
404
|
+
const lines: string[] = [
|
|
405
|
+
"These are confirmed issues from compiler/linter output — not opinions:",
|
|
406
|
+
"",
|
|
407
|
+
];
|
|
408
|
+
|
|
409
|
+
for (const f of failures) {
|
|
410
|
+
lines.push(`### ❌ ${f.check.name} (exit code ${f.exitCode})`);
|
|
411
|
+
lines.push("");
|
|
412
|
+
lines.push("```");
|
|
413
|
+
lines.push(f.output);
|
|
414
|
+
lines.push("```");
|
|
415
|
+
lines.push("");
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const passed = suite.results.filter((r) => r.passed);
|
|
419
|
+
if (passed.length > 0) {
|
|
420
|
+
lines.push(
|
|
421
|
+
`✅ Passed: ${passed.map((r) => r.check.name).join(", ")}`
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return lines.join("\n");
|
|
426
|
+
}
|