specrails-desktop 2.59.1 → 2.59.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specrails-desktop",
3
- "version": "2.59.1",
3
+ "version": "2.59.2",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -408,7 +408,9 @@ async function runAgentRuntimeInvocation(options) {
408
408
  else if (event.type === 'workflow-event') {
409
409
  const payload = event.event;
410
410
  const kind = payload?.type === 'step_succeeded' && payload.outcome === 'fail' ? 'verification_failed' : payload?.type;
411
- const detail = payload?.error?.message ?? payload?.message;
411
+ const completionDetail = payload?.type === 'workflow_failed' && Array.isArray(payload.reasons)
412
+ ? payload.reasons.filter(reason => typeof reason === 'string' && reason.trim()).join('; ').slice(0, 4_000) : undefined;
413
+ const detail = payload?.error?.message ?? payload?.message ?? completionDetail;
412
414
  const stepKey = JSON.stringify([payload?.scopeId ?? null, payload?.nodePath ?? payload?.stepId ?? null]);
413
415
  if (payload?.type === 'step_failed' && typeof detail === 'string' && detail.trim()) {
414
416
  stepFailures.delete(stepKey);
@@ -7,6 +7,7 @@ const string = { type: 'string' };
7
7
  const strings = { type: 'array', items: string, maxItems: 100 };
8
8
  const score = { type: 'number', minimum: 0, maximum: 100 };
9
9
  const aspects = { type_correctness: 60, pattern_adherence: 60, test_coverage: 60, security: 75, architectural_alignment: 60 };
10
+ const reviewPolicy = 'approved == true; issues must be empty; score >= 70; ' + Object.entries(aspects).map(([aspect, threshold]) => `${aspect} >= ${threshold}`).join('; ');
10
11
  /** The recipe, roles, schemas and decisions are Desktop data, never Core phases. */
11
12
  function configurableImplementGraph() {
12
13
  const agents = (0, loop_agents_1.defaultLoopAgents)();
@@ -31,23 +32,26 @@ function configurableImplementGraph() {
31
32
  node('clarify', 'Clarify plan', 'question', { text: '{{outputs.architect.structured.question}}' }, { next: 'reset-confidence' });
32
33
  node('reset-confidence', 'Reset confidence after answer', 'assign', { set: { planInvestigated: false } }, { next: 'architect', failed: 'failed' });
33
34
  node('validate', 'Validate OpenSpec', 'openspec-validate', { change }, { pass: 'freeze', fail: 'architect', failed: 'failed' });
34
- node('freeze', 'Freeze approved artifacts', 'artifact-contract', { change, contractId: 'plan', action: 'freeze' }, { pass: 'developer', fail: 'architect', failed: 'failed' });
35
+ node('freeze', 'Freeze approved artifacts', 'artifact-contract', { change, contractId: 'plan', action: 'freeze' }, { pass: 'initialize-corrections', fail: 'architect', failed: 'failed' });
36
+ node('initialize-corrections', 'Initialize automatic correction budget', 'assign', { set: { correctionAttempts: 0, correctionLimit: 3 } }, { next: 'developer', failed: 'failed' });
35
37
  const implementation = objectSchema({ summary: string, incomplete: strings });
36
38
  turn('developer', 'Developer', 'build', `Implement every approved task for change ${change} using the bound official apply skill. Preserve proposal, design and specs; only tick tasks you completed. Report summary and incomplete task descriptions. Prior execution evidence and human answers: {{history.text}}`, implementation, 'tasks');
37
39
  node('tasks', 'Check artifacts and completed tasks', 'artifact-contract', { change, contractId: 'plan', action: 'check', requireCompletedTasks: true }, { pass: 'verify', fail: 'developer', failed: 'failed' });
38
40
  node('verify', 'Verify candidate', 'verify', { commands: 'configured', additionalCommandsFrom: 'architect' }, { pass: 'reviewer', fail: 'correction-context', failed: 'failed' });
39
- turn('reviewer', 'Reviewer', 'assess', `Review change ${change} read-only against all approved artifacts and frozen ticket acceptance criteria. Inspect real code and the host verification evidence: {{outputs.verify}}. Return approved, summary, issues, score (0-100), and aspects. Prior execution: {{history.text}}`, objectSchema({ approved: { type: 'boolean' }, summary: string, issues: strings, score, aspects: objectSchema(Object.fromEntries(Object.keys(aspects).map(aspect => [aspect, score]))) }), 'review-policy');
41
+ turn('reviewer', 'Reviewer', 'assess', `Review change ${change} read-only against all approved artifacts and frozen ticket acceptance criteria. Inspect real code and the host verification evidence: {{outputs.verify}}. Return approved, summary, issues, score (0-100), and aspects. Acceptance policy: ${reviewPolicy}. For any rejection or below-threshold score, explain the concrete unmet requirement and actionable repair in summary and issues; passing commands do not prove every acceptance obligation. Prior execution: {{history.text}}`, objectSchema({ approved: { type: 'boolean' }, summary: string, issues: strings, score, aspects: objectSchema(Object.fromEntries(Object.keys(aspects).map(aspect => [aspect, score]))) }), 'review-policy');
40
42
  node('review-policy', 'Review acceptance policy', 'condition', { expr: ['$outputs.reviewer.structured.approved == true', '$outputs.reviewer.structured.issues.length == 0', '$outputs.reviewer.structured.score >= 70', ...Object.entries(aspects).map(([aspect, threshold]) => `$outputs.reviewer.structured.aspects.${aspect} >= ${threshold}`)].join(' && ') }, { true: 'approve', false: 'correction-context' });
41
43
  node('correction-context', 'Select current review findings', 'condition', { expr: '$outputs.verify.valid == true && exists($outputs.reviewer.candidateHash) && $outputs.reviewer.candidateHash == $outputs.verify.candidateHash' }, { true: 'capture-review', false: 'clear-review' });
42
- node('capture-review', 'Capture current review findings', 'assign', { set: { latestReview: '{{outputs.reviewer}}' } }, { next: 'fixer', failed: 'failed' });
43
- node('clear-review', 'Clear outdated review findings', 'assign', { set: { latestReview: 'No reviewer findings for the current verified candidate. Diagnose the current host verification failure first.' } }, { next: 'fixer', failed: 'failed' });
44
- turn('fixer', 'Fixer', 'correct', `Correct the reported implementation, test or review failure for change ${change} using the bound apply skill and preserving approved artifacts. Read failureSummary, command, cwd, exitCode and evidenceId first; use read_verification_evidence for truncated evidence. Reproduce the exact focused failure, inspect the assertion and tested code, then apply the smallest repair that preserves the required behavior. An unchanged filename does not prove an unrelated failure. A mandatory test inside the admitted workspace may receive a proven formatting/compatibility repair while retaining every required guard. Report diagnosis, focused commands with original exit codes, repair and unresolved failures in summary and incomplete. If the cause cannot be repaired within scope, explain the evidence and required action; an unchanged candidate with failed verification stops this run. Host verification: {{outputs.verify}}. Latest review findings for this candidate: {{run.latestReview}}. Prior execution: {{history.text}}`, implementation, 'correction-progress');
45
- node('correction-progress', 'Check correction progress', 'condition', { expr: '$outputs.verify.valid == true || (exists($outputs.fixer.candidateHash) && $outputs.fixer.candidateHash != null && $outputs.fixer.candidateHash != $outputs.verify.candidateHash)' }, { true: 'tasks', false: 'correction-stalled' });
44
+ node('capture-review', 'Capture current review findings', 'assign', { set: { latestReview: '{{outputs.reviewer.structured}}' } }, { next: 'correction-budget', failed: 'failed' });
45
+ node('clear-review', 'Clear outdated review findings', 'assign', { set: { latestReview: 'No reviewer findings for the current verified candidate. Diagnose the current host verification failure first.' } }, { next: 'correction-budget', failed: 'failed' });
46
+ node('correction-budget', 'Check automatic correction budget', 'condition', { expr: '$vars.correctionAttempts < $vars.correctionLimit' }, { true: 'begin-correction', false: 'correction-exhausted' });
47
+ node('begin-correction', 'Count automatic correction attempt', 'assign', { increment: { correctionAttempts: 1 } }, { next: 'fixer', failed: 'failed' });
48
+ turn('fixer', 'Fixer', 'correct', `Correct the reported implementation, test or review failure for change ${change} using the bound apply skill and preserving approved artifacts. Read failureSummary, command, cwd, exitCode and evidenceId first; use read_verification_evidence for truncated evidence. If Host verification is valid, repair the concrete rejected review obligation or below-threshold aspect instead of only rerunning green commands. Acceptance policy: ${reviewPolicy}. Reproduce the exact focused failure, inspect the assertion and tested code, then apply the smallest repair that preserves the required behavior. An unchanged filename does not prove an unrelated failure. A mandatory test inside the admitted workspace may receive a proven formatting/compatibility repair while retaining every required guard. Report diagnosis, focused commands with original exit codes, repair and unresolved failures in summary and incomplete. If the cause cannot be repaired within scope, explain the evidence and required action; any unchanged correction stops this run even when host checks pass. Do not make unrelated edits merely to change the candidate hash. Host verification: {{outputs.verify}}. Latest review findings for this candidate: {{run.latestReview}}. Prior execution: {{history.text}}`, implementation, 'correction-progress');
49
+ node('correction-progress', 'Check correction progress', 'condition', { expr: 'exists($outputs.fixer.candidateHash) && $outputs.fixer.candidateHash != null && $outputs.fixer.candidateHash != $outputs.verify.candidateHash' }, { true: 'tasks', false: 'correction-stalled' });
46
50
  node('approve', 'Approve archive', 'approval', { reason: `Approve archiving the reviewed and verified candidate for change ${change}.`, bindCandidate: true, enabled: agents.approvalBeforeArchive === true }, { next: 'approval-policy' });
47
51
  node('approval-policy', 'Approval decision', 'condition', { expr: '$outputs.approve.response.approved == true' }, { true: 'archive', false: 'failed' });
48
52
  node('archive', 'Archive OpenSpec', 'openspec-archive', { change, allowArchived: true, requiresVerified: true, reviewedCandidate: '{{outputs.reviewer.candidateHash}}', approvedCandidate: '{{outputs.approve.candidateHash}}' }, { next: 'verify-archive', failed: 'verify' });
49
53
  node('verify-archive', 'Verify archived candidate', 'verify', { commands: 'configured', additionalCommandsFrom: 'architect' }, { pass: 'done', fail: 'failed', failed: 'failed' });
50
- nodes.push({ id: 'done', type: 'end', position: { x: 0, y: nodes.length * 140 }, data: { outcome: 'success', requiresVerified: true } }, { id: 'failed', type: 'end', position: { x: 360, y: nodes.length * 140 }, data: { outcome: 'failure' } }, { id: 'correction-stalled', type: 'end', position: { x: 720, y: nodes.length * 140 }, data: { outcome: 'failure', reason: 'Verification remains failed and the correction made no candidate changes. {{outputs.fixer.structured.summary}}' } });
54
+ nodes.push({ id: 'done', type: 'end', position: { x: 0, y: nodes.length * 140 }, data: { outcome: 'success', requiresVerified: true } }, { id: 'failed', type: 'end', position: { x: 360, y: nodes.length * 140 }, data: { outcome: 'failure' } }, { id: 'correction-stalled', type: 'end', position: { x: 720, y: nodes.length * 140 }, data: { outcome: 'failure', reason: `Automatic correction made no candidate changes. Host verification is valid: {{outputs.verify.valid}}. Acceptance policy: ${reviewPolicy}. Latest review findings: {{run.latestReview}}. Fixer diagnosis: {{outputs.fixer.structured.summary}}` } }, { id: 'correction-exhausted', type: 'end', position: { x: 1080, y: nodes.length * 140 }, data: { outcome: 'failure', reason: `Automatic correction limit reached ({{run.correctionAttempts}}/{{run.correctionLimit}}). Acceptance policy: ${reviewPolicy}. Latest review findings: {{run.latestReview}}. Fixer diagnosis: {{outputs.fixer.structured.summary}}. Host verification: {{outputs.verify}}` } });
51
55
  edges.unshift({ id: 'e-start', source: 'start', target: 'architect' });
52
56
  return { nodes, edges, config: { maxIterations: 12, maxTransitions: 200, timeoutMinutes: 0, aiStepTimeoutMinutes: 0, change: 'new', journal: 'ledger-only', ticketScope: 'all', agents, reviewerStepId: 'reviewer' } };
53
57
  }