chati-dev 4.5.10 → 4.5.12
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/framework/agents/discover/brownfield-wu.md +1 -1
- package/framework/agents/plan/tasks.md +2 -2
- package/framework/config.yaml +2 -2
- package/framework/constitution.md +21 -22
- package/framework/context/governance.md +10 -10
- package/framework/context/quality.md +6 -6
- package/framework/context/root.md +1 -1
- package/framework/data/entity-registry.yaml +1 -1
- package/framework/domains/constitution.yaml +1 -1
- package/framework/domains/workflows/brownfield-fullstack.yaml +5 -3
- package/framework/domains/workflows/brownfield-service.yaml +2 -3
- package/framework/domains/workflows/brownfield-ui.yaml +2 -3
- package/framework/domains/workflows/greenfield-fullstack.yaml +5 -3
- package/framework/domains/workflows/quick-flow.yaml +7 -6
- package/framework/domains/workflows/standard-flow.yaml +4 -5
- package/framework/frameworks/quality-dimensions.yaml +3 -3
- package/framework/hooks/git-push-authority.js +8 -4
- package/framework/manifest.json +49 -49
- package/framework/manifest.sig +1 -1
- package/framework/orchestrator/chati.md +29 -20
- package/framework/schemas/session.schema.json +5 -5
- package/framework/workflows/brownfield-fullstack.yaml +17 -123
- package/framework/workflows/brownfield-service.yaml +15 -109
- package/framework/workflows/brownfield-ui.yaml +15 -115
- package/framework/workflows/greenfield-fullstack.yaml +17 -130
- package/framework/workflows/quick-flow.yaml +20 -111
- package/framework/workflows/standard-flow.yaml +14 -158
- package/package.json +1 -1
- package/src/config/claude-settings-generator.js +4 -3
- package/src/config/context-file-generator.js +10 -1
- package/src/config/framework-adapter.js +10 -2
- package/src/context/layers/l1-global.js +2 -0
- package/src/installer/core.js +94 -13
- package/src/installer/templates.js +5 -4
- package/src/installer/validator.js +8 -2
- package/src/intelligence/registry-manager.js +11 -4
- package/src/orchestrator/cli.js +158 -42
- package/src/orchestrator/pipeline-manager.js +2 -0
- package/src/orchestrator/runtime-installation-v2.js +38 -0
- package/src/orchestrator/session-manager.js +1 -1
- package/src/terminal/handoff-parser.js +23 -4
- package/src/terminal/prompt-builder.js +15 -1
- package/src/terminal/provider-preflight.js +78 -0
- package/src/terminal/run-agent.js +66 -23
- package/src/terminal/spawner.js +5 -1
- package/src/utils/schema-validator.js +5 -2
- package/src/wizard/index.js +1 -0
|
@@ -15,8 +15,9 @@
|
|
|
15
15
|
|
|
16
16
|
import { fileURLToPath } from 'url';
|
|
17
17
|
import { buildAgentPrompt } from './prompt-builder.js';
|
|
18
|
-
import {
|
|
18
|
+
import { isTransientFailure, spawnTerminalWithRetry } from './spawner.js';
|
|
19
19
|
import { parseAgentOutput } from './handoff-parser.js';
|
|
20
|
+
import { checkProviderReadiness } from './provider-preflight.js';
|
|
20
21
|
import { createCostTracker } from './cost-tracker.js';
|
|
21
22
|
import { getRateLimiter } from './rate-limiter.js';
|
|
22
23
|
import { initCollector, track as telemetryTrack, flush as telemetryFlush } from '../telemetry/collector.js';
|
|
@@ -104,6 +105,11 @@ async function main() {
|
|
|
104
105
|
|
|
105
106
|
// Wait for rate limit slot before spawning
|
|
106
107
|
const spawnProvider = promptResult.provider || args.provider || 'claude';
|
|
108
|
+
const readiness = checkProviderReadiness(spawnProvider, { workingDir: projectDir });
|
|
109
|
+
if (!readiness.ready) {
|
|
110
|
+
outputResult({ status: 'error', error: readiness.detail, code: readiness.code, provider: spawnProvider });
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
107
113
|
const limiter = getRateLimiter(spawnProvider);
|
|
108
114
|
if (!limiter.canSpawn()) {
|
|
109
115
|
const rateLimitStart = Date.now();
|
|
@@ -121,7 +127,7 @@ async function main() {
|
|
|
121
127
|
let handle;
|
|
122
128
|
|
|
123
129
|
try {
|
|
124
|
-
|
|
130
|
+
const spawnConfig = {
|
|
125
131
|
agent: args.agent,
|
|
126
132
|
taskId: args['task-id'],
|
|
127
133
|
model: promptResult.model,
|
|
@@ -133,31 +139,25 @@ async function main() {
|
|
|
133
139
|
providerId: args['provider-id'] || null,
|
|
134
140
|
reasoningConfiguration: args['reasoning-configuration'] || null,
|
|
135
141
|
catalogSnapshotRef: args['catalog-snapshot-ref'] || null,
|
|
142
|
+
};
|
|
143
|
+
handle = await spawnTerminalWithRetry(spawnConfig, {
|
|
144
|
+
maxRetries: 1,
|
|
145
|
+
baseDelay: 500,
|
|
146
|
+
enableModelFallback: args['strict-provider'] !== 'true',
|
|
147
|
+
shouldRetry(exitCode, output) {
|
|
148
|
+
const combined = Array.isArray(output) ? output.join('') : String(output || '');
|
|
149
|
+
return isTransientFailure(exitCode, combined) || /not logged in|not authenticated/i.test(combined);
|
|
150
|
+
},
|
|
136
151
|
});
|
|
137
152
|
} catch (err) {
|
|
138
153
|
outputError(`Failed to spawn terminal: ${err.message}`);
|
|
139
154
|
process.exit(1);
|
|
140
155
|
}
|
|
141
156
|
|
|
142
|
-
// Wait for the process to complete
|
|
143
|
-
try {
|
|
144
|
-
await waitForExit(handle, timeout);
|
|
145
|
-
} catch (err) {
|
|
146
|
-
telemetryTrack('error_occurred', {
|
|
147
|
-
errorType: 'agent_failure',
|
|
148
|
-
agent: args.agent,
|
|
149
|
-
provider: promptResult.provider || args.provider || 'claude',
|
|
150
|
-
reasoningConfiguration: args['reasoning-configuration'] || null,
|
|
151
|
-
phase: sessionState?.phase || 'unknown',
|
|
152
|
-
});
|
|
153
|
-
await flushAndSend(projectDir);
|
|
154
|
-
outputError(`Terminal execution failed: ${err.message}`);
|
|
155
|
-
process.exit(2);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
157
|
const elapsed = Date.now() - startTime;
|
|
159
158
|
const stdout = handle.stdout.join('');
|
|
160
159
|
const stderr = handle.stderr.join('');
|
|
160
|
+
const providerOutput = `${stdout}\n${stderr}`;
|
|
161
161
|
|
|
162
162
|
// Track cost metrics
|
|
163
163
|
const tracker = createCostTracker();
|
|
@@ -186,7 +186,7 @@ async function main() {
|
|
|
186
186
|
model: costRecord.model,
|
|
187
187
|
duration: elapsed,
|
|
188
188
|
score: null, // Score is determined by the gate, not the agent
|
|
189
|
-
retryCount: 0,
|
|
189
|
+
retryCount: handle.retryCount || 0,
|
|
190
190
|
pipelineType: sessionState?.isQuickFlow ? 'quick-flow' : 'standard',
|
|
191
191
|
});
|
|
192
192
|
|
|
@@ -210,7 +210,19 @@ async function main() {
|
|
|
210
210
|
// Parse the handoff from stdout
|
|
211
211
|
const parsed = parseAgentOutput(stdout);
|
|
212
212
|
|
|
213
|
-
if (
|
|
213
|
+
if (handle.exitCode !== 0 && /not logged in|not authenticated/i.test(providerOutput)) {
|
|
214
|
+
outputResult({
|
|
215
|
+
status: 'error',
|
|
216
|
+
code: 'PROVIDER_AUTH_STATE_MISMATCH',
|
|
217
|
+
error: `${spawnProvider} passed preflight but rejected the spawned execution`,
|
|
218
|
+
provider: spawnProvider,
|
|
219
|
+
exitCode: handle.exitCode,
|
|
220
|
+
retryCount: handle.retryCount || 0,
|
|
221
|
+
});
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (parsed.found && parsed.valid) {
|
|
214
226
|
outputResult({
|
|
215
227
|
status: parsed.handoff.status,
|
|
216
228
|
agent: args.agent,
|
|
@@ -222,15 +234,33 @@ async function main() {
|
|
|
222
234
|
elapsed,
|
|
223
235
|
costEstimate,
|
|
224
236
|
});
|
|
237
|
+
} else if (recoverInteractiveHandoff(args.agent, stdout, handle.exitCode)) {
|
|
238
|
+
// Interactive discovery models sometimes answer with the user-facing
|
|
239
|
+
// question but omit the machine block. Preserve the exact question while
|
|
240
|
+
// restoring the deterministic relay contract for the parent orchestrator.
|
|
241
|
+
outputResult({
|
|
242
|
+
status: 'needs_input',
|
|
243
|
+
agent: args.agent,
|
|
244
|
+
model: promptResult.model,
|
|
245
|
+
provider: promptResult.provider || args.provider || 'claude',
|
|
246
|
+
exitCode: handle.exitCode,
|
|
247
|
+
handoff: recoverInteractiveHandoff(args.agent, stdout, handle.exitCode),
|
|
248
|
+
contractRecovery: 'interactive_output_wrapped',
|
|
249
|
+
elapsed,
|
|
250
|
+
costEstimate,
|
|
251
|
+
});
|
|
225
252
|
} else {
|
|
226
|
-
//
|
|
253
|
+
// A non-interactive agent that omits the handoff violated the execution
|
|
254
|
+
// contract and cannot be treated as successful or partially complete.
|
|
227
255
|
outputResult({
|
|
228
|
-
status:
|
|
256
|
+
status: 'error',
|
|
257
|
+
code: parsed.found ? 'INVALID_HANDOFF' : 'MISSING_HANDOFF',
|
|
229
258
|
agent: args.agent,
|
|
230
259
|
model: promptResult.model,
|
|
231
260
|
provider: promptResult.provider || args.provider || 'claude',
|
|
232
261
|
exitCode: handle.exitCode,
|
|
233
|
-
handoff:
|
|
262
|
+
handoff: parsed.handoff,
|
|
263
|
+
handoffWarnings: parsed.warnings,
|
|
234
264
|
rawOutput: stdout.slice(0, 5000), // Truncate to avoid huge JSON
|
|
235
265
|
stderr: stderr.slice(0, 2000),
|
|
236
266
|
elapsed,
|
|
@@ -306,6 +336,19 @@ function outputError(message) {
|
|
|
306
336
|
process.stdout.write(JSON.stringify({ status: 'error', error: message }) + '\n');
|
|
307
337
|
}
|
|
308
338
|
|
|
339
|
+
export function recoverInteractiveHandoff(agent, stdout, exitCode) {
|
|
340
|
+
if (exitCode !== 0 || !['greenfield-wu', 'brownfield-wu', 'brief'].includes(agent) || !stdout?.trim()) return null;
|
|
341
|
+
return {
|
|
342
|
+
status: 'needs_input',
|
|
343
|
+
score: null,
|
|
344
|
+
summary: 'Interactive agent requested user input.',
|
|
345
|
+
outputs: [],
|
|
346
|
+
decisions: {},
|
|
347
|
+
blockers: [],
|
|
348
|
+
needs_input_question: stdout.trim(),
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
309
352
|
/**
|
|
310
353
|
* Minimal YAML parser for session.yaml (handles flat and one-level nested keys).
|
|
311
354
|
*/
|
package/src/terminal/spawner.js
CHANGED
|
@@ -579,11 +579,13 @@ export async function spawnTerminalWithRetry(config, retryOptions = {}) {
|
|
|
579
579
|
|
|
580
580
|
// Success — return immediately
|
|
581
581
|
if (handle.exitCode === 0) {
|
|
582
|
+
handle.retryCount = attempt;
|
|
582
583
|
return handle;
|
|
583
584
|
}
|
|
584
585
|
|
|
585
586
|
// Check if failure is transient and retries remain
|
|
586
|
-
|
|
587
|
+
const combinedOutput = [...(handle.stdout || []), ...(handle.stderr || [])];
|
|
588
|
+
if (attempt < maxRetries && shouldRetry(handle.exitCode, combinedOutput, handle)) {
|
|
587
589
|
const delay = baseDelay * Math.pow(2, attempt);
|
|
588
590
|
await new Promise(resolve => setTimeout(resolve, delay));
|
|
589
591
|
continue;
|
|
@@ -617,9 +619,11 @@ export async function spawnTerminalWithRetry(config, retryOptions = {}) {
|
|
|
617
619
|
timestamp: new Date().toISOString(),
|
|
618
620
|
};
|
|
619
621
|
|
|
622
|
+
fallbackHandle.retryCount = maxRetries;
|
|
620
623
|
return fallbackHandle;
|
|
621
624
|
}
|
|
622
625
|
}
|
|
623
626
|
|
|
627
|
+
if (lastHandle) lastHandle.retryCount = lastHandle.retryCount ?? maxRetries;
|
|
624
628
|
return lastHandle;
|
|
625
629
|
}
|
|
@@ -169,7 +169,7 @@ export const SESSION_SCHEMA = {
|
|
|
169
169
|
properties: {
|
|
170
170
|
project: { type: 'string', required: true, minLength: 1 },
|
|
171
171
|
language: { type: 'string', required: true, default: 'en' },
|
|
172
|
-
pipeline_phase: { type: 'string', enum: ['discover', 'plan', 'build', 'deploy', 'completed'] },
|
|
172
|
+
pipeline_phase: { type: 'string', enum: ['discover', 'plan', 'rail', 'release', 'build', 'validate', 'deploy', 'completed'] },
|
|
173
173
|
current_agent: { type: 'string' },
|
|
174
174
|
governance_mode: { type: 'string', enum: ['planning', 'build', 'deploy'] },
|
|
175
175
|
execution_mode: { type: 'string', enum: ['interactive', 'autonomous'] },
|
|
@@ -201,7 +201,10 @@ export const CONFIG_SCHEMA = {
|
|
|
201
201
|
export const HANDOFF_SCHEMA = {
|
|
202
202
|
required: ['status'],
|
|
203
203
|
properties: {
|
|
204
|
-
status: { type: 'string', required: true, enum: [
|
|
204
|
+
status: { type: 'string', required: true, enum: [
|
|
205
|
+
'complete', 'partial', 'needs_input', 'error', 'unknown',
|
|
206
|
+
'APPROVED', 'NEEDS_REVISION', 'BLOCKED',
|
|
207
|
+
] },
|
|
205
208
|
score: { type: 'number', min: 0, max: 100 },
|
|
206
209
|
summary: { type: 'string', maxLength: 2000 },
|
|
207
210
|
outputs: { type: 'array' },
|
package/src/wizard/index.js
CHANGED