Skip to content

Commit 7815ebc

Browse files
committed
build: updated ai configuration
1 parent 1f26528 commit 7815ebc

45 files changed

Lines changed: 7787 additions & 6126 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.codex/agents/ci-monitor-subagent.toml

Lines changed: 25 additions & 592 deletions
Large diffs are not rendered by default.

.codex/config.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ args = [ "nx-mcp@latest", "--minimal" ]
66
multi_agent = true
77

88
[agents.ci-monitor-subagent]
9-
description = "Polls Nx Cloud CI pipeline and self-healing status. Returns structured state when actionable. Spawned by /monitor-ci command to monitor CI Attempt status."
9+
description = "CI helper for /monitor-ci. Fetches CI status, retrieves fix details, or updates self-healing fixes. Executes one MCP tool call and returns the result."
1010
config_file = "agents/ci-monitor-subagent.toml"

.cursor/agents/ci-monitor-subagent.md

Lines changed: 26 additions & 593 deletions
Large diffs are not rendered by default.

.cursor/commands/monitor-ci.md

Lines changed: 224 additions & 468 deletions
Large diffs are not rendered by default.

.cursor/skills/monitor-ci/SKILL.md

Lines changed: 224 additions & 468 deletions
Large diffs are not rendered by default.
Lines changed: 369 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,369 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* CI Poll Decision Script
5+
*
6+
* Deterministic decision engine for CI monitoring.
7+
* Takes ci_information JSON + state args, outputs a single JSON action line.
8+
*
9+
* Architecture:
10+
* classify() — pure decision tree, returns { action, code, extra? }
11+
* buildOutput() — maps classification to full output with messages, delays, counters
12+
*
13+
* Usage:
14+
* node ci-poll-decide.mjs '<ci_info_json>' <poll_count> <verbosity> \
15+
* [--wait-mode] [--prev-cipe-url <url>] [--expected-sha <sha>] \
16+
* [--prev-status <status>] [--timeout <seconds>] [--new-cipe-timeout <seconds>] \
17+
* [--env-rerun-count <n>] [--no-progress-count <n>] \
18+
* [--prev-cipe-status <status>] [--prev-sh-status <status>] \
19+
* [--prev-verification-status <status>] [--prev-failure-classification <status>]
20+
*/
21+
22+
// --- Arg parsing ---
23+
24+
const args = process.argv.slice(2);
25+
const ciInfoJson = args[0];
26+
const pollCount = parseInt(args[1], 10) || 0;
27+
const verbosity = args[2] || "medium";
28+
29+
function getFlag(name) {
30+
return args.includes(name);
31+
}
32+
33+
function getArg(name) {
34+
const idx = args.indexOf(name);
35+
return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : null;
36+
}
37+
38+
const waitMode = getFlag("--wait-mode");
39+
const prevCipeUrl = getArg("--prev-cipe-url");
40+
const expectedSha = getArg("--expected-sha");
41+
const prevStatus = getArg("--prev-status");
42+
const timeoutSeconds = parseInt(getArg("--timeout") || "0", 10);
43+
const newCipeTimeoutSeconds = parseInt(getArg("--new-cipe-timeout") || "0", 10);
44+
const envRerunCount = parseInt(getArg("--env-rerun-count") || "0", 10);
45+
const inputNoProgressCount = parseInt(getArg("--no-progress-count") || "0", 10);
46+
const prevCipeStatus = getArg("--prev-cipe-status");
47+
const prevShStatus = getArg("--prev-sh-status");
48+
const prevVerificationStatus = getArg("--prev-verification-status");
49+
const prevFailureClassification = getArg("--prev-failure-classification");
50+
51+
// --- Parse CI info ---
52+
53+
let ci;
54+
try {
55+
ci = JSON.parse(ciInfoJson);
56+
} catch {
57+
console.log(
58+
JSON.stringify({
59+
action: "done",
60+
code: "error",
61+
message: "Failed to parse ci_information JSON",
62+
noProgressCount: inputNoProgressCount + 1,
63+
envRerunCount,
64+
}),
65+
);
66+
process.exit(0);
67+
}
68+
69+
const {
70+
cipeStatus,
71+
selfHealingStatus,
72+
verificationStatus,
73+
selfHealingEnabled,
74+
selfHealingSkippedReason,
75+
failureClassification: rawFailureClassification,
76+
failedTaskIds = [],
77+
verifiedTaskIds = [],
78+
couldAutoApplyTasks,
79+
userAction,
80+
cipeUrl,
81+
commitSha,
82+
} = ci;
83+
84+
const failureClassification = rawFailureClassification?.toLowerCase() ?? null;
85+
86+
// --- Helpers ---
87+
88+
function categorizeTasks() {
89+
const verifiedSet = new Set(verifiedTaskIds);
90+
const unverified = failedTaskIds.filter((t) => !verifiedSet.has(t));
91+
if (unverified.length === 0) return { category: "all_verified" };
92+
93+
const e2e = unverified.filter((t) => {
94+
const parts = t.split(":");
95+
return parts.length >= 2 && parts[1].includes("e2e");
96+
});
97+
if (e2e.length === unverified.length) return { category: "e2e_only" };
98+
99+
const verifiable = unverified.filter((t) => {
100+
const parts = t.split(":");
101+
return !(parts.length >= 2 && parts[1].includes("e2e"));
102+
});
103+
return { category: "needs_local_verify", verifiableTaskIds: verifiable };
104+
}
105+
106+
function backoff(count) {
107+
const delays = [60, 90, 120];
108+
return delays[Math.min(count, delays.length - 1)];
109+
}
110+
111+
function hasStateChanged() {
112+
if (prevCipeStatus && cipeStatus !== prevCipeStatus) return true;
113+
if (prevShStatus && selfHealingStatus !== prevShStatus) return true;
114+
if (prevVerificationStatus && verificationStatus !== prevVerificationStatus) return true;
115+
if (prevFailureClassification && failureClassification !== prevFailureClassification) return true;
116+
return false;
117+
}
118+
119+
function isTimedOut() {
120+
if (timeoutSeconds <= 0) return false;
121+
const avgDelay = pollCount === 0 ? 0 : backoff(Math.floor(pollCount / 2));
122+
return pollCount * avgDelay >= timeoutSeconds;
123+
}
124+
125+
function isWaitTimedOut() {
126+
if (newCipeTimeoutSeconds <= 0) return false;
127+
return pollCount * 30 >= newCipeTimeoutSeconds;
128+
}
129+
130+
function isNewCipe() {
131+
return (prevCipeUrl && cipeUrl && cipeUrl !== prevCipeUrl) || (expectedSha && commitSha && commitSha === expectedSha);
132+
}
133+
134+
// ============================================================
135+
// classify() — pure decision tree
136+
//
137+
// Returns: { action: 'poll'|'wait'|'done', code: string, extra? }
138+
//
139+
// Decision priority (top wins):
140+
// WAIT MODE:
141+
// 1. new CI Attempt detected → poll (new_cipe_detected)
142+
// 2. wait timed out → done (no_new_cipe)
143+
// 3. still waiting → wait (waiting_for_cipe)
144+
// NORMAL MODE:
145+
// 4. polling timeout → done (polling_timeout)
146+
// 5. circuit breaker (5 polls) → done (circuit_breaker)
147+
// 6. CI succeeded → done (ci_success)
148+
// 7. CI canceled → done (cipe_canceled)
149+
// 8. CI timed out → done (cipe_timed_out)
150+
// 9. CI failed, no tasks recorded → done (cipe_no_tasks)
151+
// 10. environment failure → done (environment_rerun_cap | environment_issue)
152+
// 11. self-healing throttled → done (self_healing_throttled)
153+
// 12. CI in progress / not started → poll (ci_running)
154+
// 13. self-healing in progress → poll (sh_running)
155+
// 14. flaky task auto-rerun → poll (flaky_rerun)
156+
// 15. fix auto-applied → poll (fix_auto_applied)
157+
// 16. auto-apply: verification pending→ poll (verification_pending)
158+
// 17. auto-apply: verified → done (fix_auto_applying)
159+
// 18. fix: verification failed/none → done (fix_needs_review)
160+
// 19. fix: all/e2e verified → done (fix_apply_ready)
161+
// 20. fix: needs local verify → done (fix_needs_local_verify)
162+
// 21. self-healing failed → done (fix_failed)
163+
// 22. no fix available → done (no_fix)
164+
// 23. fallback → poll (fallback)
165+
// ============================================================
166+
167+
function classify() {
168+
// --- Wait mode ---
169+
if (waitMode) {
170+
if (isNewCipe()) return { action: "poll", code: "new_cipe_detected" };
171+
if (isWaitTimedOut()) return { action: "done", code: "no_new_cipe" };
172+
return { action: "wait", code: "waiting_for_cipe" };
173+
}
174+
175+
// --- Guards ---
176+
if (isTimedOut()) return { action: "done", code: "polling_timeout" };
177+
if (noProgressCount >= 5) return { action: "done", code: "circuit_breaker" };
178+
179+
// --- Terminal CI states ---
180+
if (cipeStatus === "SUCCEEDED") return { action: "done", code: "ci_success" };
181+
if (cipeStatus === "CANCELED") return { action: "done", code: "cipe_canceled" };
182+
if (cipeStatus === "TIMED_OUT") return { action: "done", code: "cipe_timed_out" };
183+
184+
// --- CI failed, no tasks ---
185+
if (cipeStatus === "FAILED" && failedTaskIds.length === 0 && selfHealingStatus == null)
186+
return { action: "done", code: "cipe_no_tasks" };
187+
188+
// --- Environment failure ---
189+
if (failureClassification === "environment_state") {
190+
if (envRerunCount >= 2) return { action: "done", code: "environment_rerun_cap" };
191+
return { action: "done", code: "environment_issue" };
192+
}
193+
194+
// --- Throttled ---
195+
if (selfHealingSkippedReason === "THROTTLED") return { action: "done", code: "self_healing_throttled" };
196+
197+
// --- Still running: CI ---
198+
if (cipeStatus === "IN_PROGRESS" || cipeStatus === "NOT_STARTED") return { action: "poll", code: "ci_running" };
199+
200+
// --- Still running: self-healing ---
201+
if ((selfHealingStatus === "IN_PROGRESS" || selfHealingStatus === "NOT_STARTED") && !selfHealingSkippedReason)
202+
return { action: "poll", code: "sh_running" };
203+
204+
// --- Still running: flaky rerun ---
205+
if (failureClassification === "flaky_task") return { action: "poll", code: "flaky_rerun" };
206+
207+
// --- Fix auto-applied, waiting for new CI Attempt ---
208+
if (userAction === "APPLIED_AUTOMATICALLY") return { action: "poll", code: "fix_auto_applied" };
209+
210+
// --- Auto-apply path (couldAutoApplyTasks) ---
211+
if (couldAutoApplyTasks === true) {
212+
if (verificationStatus === "NOT_STARTED" || verificationStatus === "IN_PROGRESS")
213+
return { action: "poll", code: "verification_pending" };
214+
if (verificationStatus === "COMPLETED") return { action: "done", code: "fix_auto_applying" };
215+
// verification FAILED or NOT_EXECUTABLE → falls through to fix_needs_review
216+
}
217+
218+
// --- Fix available ---
219+
if (selfHealingStatus === "COMPLETED") {
220+
if (
221+
verificationStatus === "FAILED" ||
222+
verificationStatus === "NOT_EXECUTABLE" ||
223+
(couldAutoApplyTasks !== true && !verificationStatus)
224+
)
225+
return { action: "done", code: "fix_needs_review" };
226+
227+
const tasks = categorizeTasks();
228+
if (tasks.category === "all_verified" || tasks.category === "e2e_only")
229+
return { action: "done", code: "fix_apply_ready" };
230+
return {
231+
action: "done",
232+
code: "fix_needs_local_verify",
233+
extra: { verifiableTaskIds: tasks.verifiableTaskIds },
234+
};
235+
}
236+
237+
// --- Fix failed ---
238+
if (selfHealingStatus === "FAILED") return { action: "done", code: "fix_failed" };
239+
240+
// --- No fix available ---
241+
if (cipeStatus === "FAILED" && (selfHealingEnabled === false || selfHealingStatus === "NOT_EXECUTABLE"))
242+
return { action: "done", code: "no_fix" };
243+
244+
// --- Fallback ---
245+
return { action: "poll", code: "fallback" };
246+
}
247+
248+
// ============================================================
249+
// buildOutput() — maps classification to full JSON output
250+
// ============================================================
251+
252+
// Message templates keyed by status or key
253+
const messages = {
254+
// wait mode
255+
new_cipe_detected: () => `New CI Attempt detected! CI: ${cipeStatus || "N/A"}`,
256+
no_new_cipe: () => "New CI Attempt timeout exceeded. No new CI Attempt detected.",
257+
waiting_for_cipe: () => "Waiting for new CI Attempt...",
258+
259+
// guards
260+
polling_timeout: () => "Polling timeout exceeded.",
261+
circuit_breaker: () => "No progress after 5 consecutive polls. Stopping.",
262+
263+
// terminal
264+
ci_success: () => "CI passed successfully!",
265+
cipe_canceled: () => "CI Attempt was canceled.",
266+
cipe_timed_out: () => "CI Attempt timed out.",
267+
cipe_no_tasks: () => "CI failed but no Nx tasks were recorded.",
268+
269+
// environment
270+
environment_rerun_cap: () => "Environment rerun cap (2) exceeded. Bailing.",
271+
environment_issue: () => "CI: FAILED | Classification: ENVIRONMENT_STATE",
272+
273+
// throttled
274+
self_healing_throttled: () => "Self-healing throttled \u2014 too many unapplied fixes.",
275+
276+
// polling
277+
ci_running: () => `CI: ${cipeStatus}`,
278+
sh_running: () => `CI: ${cipeStatus} | Self-healing: ${selfHealingStatus}`,
279+
flaky_rerun: () => "CI: FAILED | Classification: FLAKY_TASK (auto-rerun in progress)",
280+
fix_auto_applied: () => "CI: FAILED | Fix auto-applied, new CI Attempt spawning",
281+
verification_pending: () => `CI: FAILED | Self-healing: COMPLETED | Verification: ${verificationStatus}`,
282+
283+
// actionable
284+
fix_auto_applying: () => "Fix verified! Auto-applying...",
285+
fix_needs_review: () => `Fix available but needs review. Verification: ${verificationStatus || "N/A"}`,
286+
fix_apply_ready: () => "Fix available and verified. Ready to apply.",
287+
fix_needs_local_verify: (extra) =>
288+
`Fix available. ${extra.verifiableTaskIds.length} task(s) need local verification.`,
289+
fix_failed: () => "Self-healing failed to generate a fix.",
290+
no_fix: () => "CI failed, no fix available.",
291+
292+
// fallback
293+
fallback: () =>
294+
`CI: ${cipeStatus || "N/A"} | Self-healing: ${
295+
selfHealingStatus || "N/A"
296+
} | Verification: ${verificationStatus || "N/A"}`,
297+
};
298+
299+
// Codes where noProgressCount resets to 0 (genuine progress occurred)
300+
const resetProgressCodes = new Set([
301+
"ci_success",
302+
"fix_auto_applying",
303+
"fix_needs_review",
304+
"fix_apply_ready",
305+
"fix_needs_local_verify",
306+
]);
307+
308+
function formatMessage(msg) {
309+
if (verbosity === "minimal") {
310+
const currentStatus = `${cipeStatus}|${selfHealingStatus}|${verificationStatus}`;
311+
if (currentStatus === (prevStatus || "")) return null;
312+
return msg;
313+
}
314+
if (verbosity === "verbose") {
315+
return [
316+
`Poll #${pollCount + 1} | CI: ${cipeStatus || "N/A"} | Self-healing: ${
317+
selfHealingStatus || "N/A"
318+
} | Verification: ${verificationStatus || "N/A"}`,
319+
msg,
320+
].join("\n");
321+
}
322+
return `Poll #${pollCount + 1} | ${msg}`;
323+
}
324+
325+
function buildOutput(decision) {
326+
const { action, code, extra } = decision;
327+
328+
// noProgressCount is already computed before classify() was called.
329+
// Here we only handle the reset for "genuine progress" done-codes.
330+
331+
const msgFn = messages[code];
332+
const rawMsg = msgFn ? msgFn(extra) : `Unknown: ${code}`;
333+
const message = formatMessage(rawMsg);
334+
335+
const result = {
336+
action,
337+
code,
338+
message,
339+
noProgressCount: resetProgressCodes.has(code) ? 0 : noProgressCount,
340+
envRerunCount,
341+
};
342+
343+
// Add delay
344+
if (action === "wait") {
345+
result.delay = 30;
346+
} else if (action === "poll") {
347+
result.delay = code === "new_cipe_detected" ? 60 : backoff(noProgressCount);
348+
result.fields = "light";
349+
}
350+
351+
// Add extras
352+
if (code === "new_cipe_detected") result.newCipeDetected = true;
353+
if (extra?.verifiableTaskIds) result.verifiableTaskIds = extra.verifiableTaskIds;
354+
355+
console.log(JSON.stringify(result));
356+
}
357+
358+
// --- Run ---
359+
360+
// Compute noProgressCount from input. Single assignment, no mutation.
361+
// Wait mode: reset on new cipe, otherwise unchanged (wait doesn't count as no-progress).
362+
// Normal mode: reset on any state change, otherwise increment.
363+
const noProgressCount = (() => {
364+
if (waitMode) return isNewCipe() ? 0 : inputNoProgressCount;
365+
if (isNewCipe() || hasStateChanged()) return 0;
366+
return inputNoProgressCount + 1;
367+
})();
368+
369+
buildOutput(classify());

0 commit comments

Comments
 (0)