-
Notifications
You must be signed in to change notification settings - Fork 0
fix: recover scheduled evolution reviews and close render locks #82
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c447162
e25be7f
0060a6f
aff5493
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -439,6 +439,15 @@ async function firstExistingFile(paths: string[]): Promise<string | null> { | |
| return null; | ||
| } | ||
|
|
||
| export class UnsupportedProposalTargetError extends Error { | ||
| constructor(pathValue: string) { | ||
| super( | ||
| `Automatic drafting and apply support markdown targets only: ${pathValue}. Keep this proposal for manual implementation.` | ||
| ); | ||
| this.name = "UnsupportedProposalTargetError"; | ||
| } | ||
| } | ||
|
|
||
| function supportedDraftTarget(pathValue: string): boolean { | ||
| return pathValue.toLowerCase().endsWith(".md"); | ||
| } | ||
|
|
@@ -2177,9 +2186,7 @@ async function resolveProposalTargetNode( | |
| throw new Error(`Could not resolve target path for ${target}`); | ||
| } | ||
| if (!supportedDraftTarget(pathValue)) { | ||
| throw new Error( | ||
| `Apply currently supports markdown targets only: ${pathValue}` | ||
| ); | ||
| throw new UnsupportedProposalTargetError(pathValue); | ||
| } | ||
| return { | ||
| ...node, | ||
|
|
@@ -2825,6 +2832,7 @@ Usage: | |
| fclt ai loop activity [--all|--global|--project] [--json] | ||
| fclt ai loop resolve <activity-action-locator> [--json] | ||
| fclt ai loop history [--all|--global|--project] [--since <date>] [--until <date>] [--item <id>] [--scope-id <opaque-id>] [--event <type>] [--limit <1-200>] [--cursor <cursor>] [--json] | ||
| fclt ai loop preflight [--json] | ||
| fclt ai loop run [--since <date>] [--until <date>] [--source <configured-id>] [--dry-run] [--scheduled] [--json] | ||
|
|
||
| The loop keeps a full machine-local review queue and emits a delta for | ||
|
|
@@ -2990,27 +2998,44 @@ async function loopCommand(argv: string[]) { | |
| } | ||
| return; | ||
| } | ||
| const rootDir = resolveCliContextRoot({ | ||
| rootArg: parsed.rootArg, | ||
| scope: parsed.scope, | ||
| cwd: process.cwd(), | ||
| }); | ||
| const homeDir = process.env.HOME ?? ""; | ||
| const loopScope = | ||
| parsed.scope === "global" || parsed.scope === "project" | ||
| ? parsed.scope | ||
| : projectRootFromAiRoot(rootDir, homeDir) | ||
| ? "project" | ||
| : "global"; | ||
| const json = commandArgs.includes("--json"); | ||
| const { | ||
| disableEvolutionLoop, | ||
| enableEvolutionLoop, | ||
| evolutionLoopStatus, | ||
| latestEvolutionLoopReport, | ||
| runEvolutionLoop, | ||
| } = await import("./evolution-loop"); | ||
| try { | ||
| const rootDir = resolveCliContextRoot({ | ||
| rootArg: parsed.rootArg, | ||
| scope: parsed.scope, | ||
| cwd: process.cwd(), | ||
| }); | ||
| const homeDir = process.env.HOME ?? ""; | ||
| const loopScope = | ||
| parsed.scope === "global" || parsed.scope === "project" | ||
| ? parsed.scope | ||
| : projectRootFromAiRoot(rootDir, homeDir) | ||
| ? "project" | ||
| : "global"; | ||
| const { | ||
| disableEvolutionLoop, | ||
| enableEvolutionLoop, | ||
| evolutionLoopStatus, | ||
| latestEvolutionLoopReport, | ||
| runEvolutionLoop, | ||
| } = await import("./evolution-loop"); | ||
| if (sub === "preflight") { | ||
| const { preflightEvolutionLoop } = await import("./evolution-preflight"); | ||
| const result = await preflightEvolutionLoop({ | ||
| homeDir, | ||
| rootDir, | ||
| scope: loopScope, | ||
| }); | ||
| await writeCliOutput( | ||
| json | ||
| ? JSON.stringify(result, null, 2) | ||
| : `loop preflight: ${result.status}\n${result.recovery ?? "Required paths are writable"}` | ||
| ); | ||
| if (result.status !== "ready") { | ||
| process.exitCode = 1; | ||
| } | ||
| return; | ||
| } | ||
| if (sub === "enable") { | ||
| const result = await enableEvolutionLoop({ | ||
| homeDir, | ||
|
|
@@ -3020,7 +3045,7 @@ async function loopCommand(argv: string[]) { | |
| sourceIds: parseRepeatedFlag(commandArgs, "--source"), | ||
| dryRun: commandArgs.includes("--dry-run"), | ||
| }); | ||
| console.log( | ||
| await writeCliOutput( | ||
| json | ||
| ? JSON.stringify(result, null, 2) | ||
| : `${result.dryRun ? "Would enable" : "Enabled"} evolution loop at ${result.automationPath}` | ||
|
|
@@ -3037,7 +3062,7 @@ async function loopCommand(argv: string[]) { | |
| if (!(result.dryRun || result.scheduler?.paused || !result.config)) { | ||
| process.exitCode = 1; | ||
| } | ||
| console.log( | ||
| await writeCliOutput( | ||
| json | ||
| ? JSON.stringify(result, null, 2) | ||
| : result.config | ||
|
|
@@ -3054,7 +3079,7 @@ async function loopCommand(argv: string[]) { | |
| rootDir, | ||
| scope: loopScope, | ||
| }); | ||
| console.log( | ||
| await writeCliOutput( | ||
| json | ||
| ? JSON.stringify(result, null, 2) | ||
| : [ | ||
|
|
@@ -3075,7 +3100,7 @@ async function loopCommand(argv: string[]) { | |
| if (!result) { | ||
| throw new Error("No evolution loop report has been recorded"); | ||
| } | ||
| console.log( | ||
| await writeCliOutput( | ||
| json | ||
| ? JSON.stringify(result, null, 2) | ||
| : [ | ||
|
|
@@ -3225,7 +3250,7 @@ async function loopCommand(argv: string[]) { | |
| dryRun: commandArgs.includes("--dry-run"), | ||
| trigger: commandArgs.includes("--scheduled") ? "scheduled" : "manual", | ||
| }); | ||
| console.log( | ||
| await writeCliOutput( | ||
| json | ||
| ? JSON.stringify(result, null, 2) | ||
| : [ | ||
|
|
@@ -3243,7 +3268,25 @@ async function loopCommand(argv: string[]) { | |
| } | ||
| throw new Error(`Unknown loop command: ${sub}`); | ||
| } catch (error) { | ||
| console.error(error instanceof Error ? error.message : String(error)); | ||
| const message = error instanceof Error ? error.message : String(error); | ||
| if (json && (sub === "run" || sub === "preflight")) { | ||
| await writeCliOutput( | ||
| JSON.stringify( | ||
| { | ||
| status: "failed", | ||
| phase: "command", | ||
| queueAvailable: false, | ||
| error: message, | ||
| recovery: | ||
| "Run fclt ai loop preflight for this scope in the same execution environment before another review.", | ||
| }, | ||
| null, | ||
| 2 | ||
| ) | ||
| ); | ||
| } else { | ||
| console.error(message); | ||
| } | ||
| process.exitCode = 1; | ||
| } | ||
| } | ||
|
|
@@ -3747,23 +3790,49 @@ export async function aiCommand( | |
| } | ||
|
|
||
| if (!rootScopeActive) { | ||
| const parsed = parseCliContextArgs(rest); | ||
| const homeDir = process.env.HOME ?? ""; | ||
| const rootDir = resolveCliContextRoot({ | ||
| homeDir, | ||
| rootArg: parsed.rootArg, | ||
| scope: parsed.scope, | ||
| cwd: process.cwd(), | ||
| }); | ||
| const scope = resolveCliContextScope({ | ||
| homeDir, | ||
| rootDir, | ||
| scope: parsed.scope, | ||
| }); | ||
| await withFacultRootScope({ rootDir, scope }, async () => | ||
| aiCommand(argv, true) | ||
| ); | ||
| return; | ||
| try { | ||
| const parsed = parseCliContextArgs(rest); | ||
| const homeDir = process.env.HOME ?? ""; | ||
| const rootDir = resolveCliContextRoot({ | ||
| homeDir, | ||
| rootArg: parsed.rootArg, | ||
| scope: parsed.scope, | ||
| cwd: process.cwd(), | ||
| }); | ||
| const scope = resolveCliContextScope({ | ||
| homeDir, | ||
| rootDir, | ||
| scope: parsed.scope, | ||
| }); | ||
| await withFacultRootScope({ rootDir, scope }, async () => | ||
| aiCommand(argv, true) | ||
| ); | ||
| return; | ||
| } catch (error) { | ||
| if ( | ||
| sub !== "loop" || | ||
| !rest.includes("--json") || | ||
| (rest[0] !== "run" && rest[0] !== "preflight") | ||
| ) { | ||
|
Comment on lines
+3812
to
+3816
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Fresh evidence after the context-boundary fix is that AGENTS.md reference: AGENTS.md:L130-L130 Useful? React with 👍 / 👎. |
||
| throw error; | ||
| } | ||
| await writeCliOutput( | ||
| JSON.stringify( | ||
| { | ||
| status: "failed", | ||
| phase: "command", | ||
| queueAvailable: false, | ||
| error: error instanceof Error ? error.message : String(error), | ||
| recovery: | ||
| "Resolve the project or global scope before running fclt ai loop preflight in the same execution environment.", | ||
| }, | ||
| null, | ||
| 2 | ||
| ) | ||
| ); | ||
| process.exitCode = 1; | ||
| return; | ||
| } | ||
| } | ||
|
|
||
| if (sub === "writeback") { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new JSON error handler only covers exceptions raised after entering this
try, whileparseCliContextArgs,resolveCliContextRoot, scope inference, and the evolution-loop import all run beforehand. For example,loop run --project --jsonfrom a directory without a resolvable project throws during context resolution and produces an unstructured top-level error instead of the promised{status:"failed", queueAvailable:false}response. Move context setup inside the JSON-aware error boundary so automation can reliably parse every pre-report failure.AGENTS.md reference: AGENTS.md:L130-L130
Useful? React with 👍 / 👎.