loopforge 1.3.1 → 1.8.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.
Files changed (91) hide show
  1. package/README.md +64 -3
  2. package/dist/backends/fs.d.ts +5 -0
  3. package/dist/backends/fs.d.ts.map +1 -1
  4. package/dist/backends/fs.js +66 -16
  5. package/dist/backends/fs.js.map +1 -1
  6. package/dist/builder.d.ts +0 -1
  7. package/dist/builder.d.ts.map +1 -1
  8. package/dist/builder.js +14 -25
  9. package/dist/builder.js.map +1 -1
  10. package/dist/engine.d.ts +28 -5
  11. package/dist/engine.d.ts.map +1 -1
  12. package/dist/engine.js +303 -105
  13. package/dist/engine.js.map +1 -1
  14. package/dist/index.d.ts +9 -12
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +7 -10
  17. package/dist/index.js.map +1 -1
  18. package/dist/loop-compiler.d.ts +28 -2
  19. package/dist/loop-compiler.d.ts.map +1 -1
  20. package/dist/loop-compiler.js +718 -38
  21. package/dist/loop-compiler.js.map +1 -1
  22. package/dist/mcp/server.d.ts.map +1 -1
  23. package/dist/mcp/server.js +6 -4
  24. package/dist/mcp/server.js.map +1 -1
  25. package/dist/mcp/session.d.ts +24 -4
  26. package/dist/mcp/session.d.ts.map +1 -1
  27. package/dist/mcp/session.js +303 -34
  28. package/dist/mcp/session.js.map +1 -1
  29. package/dist/mcp/tools.d.ts +165 -1
  30. package/dist/mcp/tools.d.ts.map +1 -1
  31. package/dist/mcp/tools.js +144 -15
  32. package/dist/mcp/tools.js.map +1 -1
  33. package/dist/memory-bridge.d.ts +47 -0
  34. package/dist/memory-bridge.d.ts.map +1 -0
  35. package/dist/memory-bridge.js +290 -0
  36. package/dist/memory-bridge.js.map +1 -0
  37. package/dist/observability.d.ts +23 -0
  38. package/dist/observability.d.ts.map +1 -0
  39. package/dist/observability.js +29 -0
  40. package/dist/observability.js.map +1 -0
  41. package/dist/policy.d.ts +72 -0
  42. package/dist/policy.d.ts.map +1 -1
  43. package/dist/policy.js +44 -0
  44. package/dist/policy.js.map +1 -1
  45. package/dist/protocol.d.ts +197 -7
  46. package/dist/protocol.d.ts.map +1 -1
  47. package/dist/protocol.js +48 -5
  48. package/dist/protocol.js.map +1 -1
  49. package/dist/runtime.d.ts +19 -0
  50. package/dist/runtime.d.ts.map +1 -1
  51. package/dist/runtime.js +446 -142
  52. package/dist/runtime.js.map +1 -1
  53. package/dist/tests/builder.test.js +1 -25
  54. package/dist/tests/builder.test.js.map +1 -1
  55. package/dist/tests/engine.test.js +213 -38
  56. package/dist/tests/engine.test.js.map +1 -1
  57. package/dist/tests/generate-schema.test.js +4 -4
  58. package/dist/tests/generate-schema.test.js.map +1 -1
  59. package/dist/tests/loop-compiler.test.js +427 -2
  60. package/dist/tests/loop-compiler.test.js.map +1 -1
  61. package/dist/tests/mcp.test.js +166 -79
  62. package/dist/tests/mcp.test.js.map +1 -1
  63. package/dist/tests/memory-bridge.test.d.ts +8 -0
  64. package/dist/tests/memory-bridge.test.d.ts.map +1 -0
  65. package/dist/tests/memory-bridge.test.js +384 -0
  66. package/dist/tests/memory-bridge.test.js.map +1 -0
  67. package/dist/tests/protocol.test.js +14 -5
  68. package/dist/tests/protocol.test.js.map +1 -1
  69. package/dist/tests/verification-gate.test.d.ts +2 -0
  70. package/dist/tests/verification-gate.test.d.ts.map +1 -0
  71. package/dist/tests/verification-gate.test.js +407 -0
  72. package/dist/tests/verification-gate.test.js.map +1 -0
  73. package/dist/verification-gate.d.ts +26 -0
  74. package/dist/verification-gate.d.ts.map +1 -0
  75. package/dist/verification-gate.js +232 -0
  76. package/dist/verification-gate.js.map +1 -0
  77. package/loop_policy.json +31 -0
  78. package/package.json +1 -2
  79. package/skills/perception/SKILL.md +147 -55
  80. package/dist/adapter.d.ts +0 -17
  81. package/dist/adapter.d.ts.map +0 -1
  82. package/dist/adapter.js +0 -228
  83. package/dist/adapter.js.map +0 -1
  84. package/dist/cli.d.ts +0 -7
  85. package/dist/cli.d.ts.map +0 -1
  86. package/dist/cli.js +0 -458
  87. package/dist/cli.js.map +0 -1
  88. package/dist/tests/adapter.test.d.ts +0 -2
  89. package/dist/tests/adapter.test.d.ts.map +0 -1
  90. package/dist/tests/adapter.test.js +0 -104
  91. package/dist/tests/adapter.test.js.map +0 -1
package/dist/engine.js CHANGED
@@ -1,22 +1,22 @@
1
1
  /** LoopForge-loop_compile — Engine (outer loop manager).
2
2
  *
3
- * v1.0: 3-mode engine with vault-backed loop lineage persistence.
4
- * invokeLoopCompile (primary), invokeBuild (internal), invokeFeedback.
3
+ * 2-mode engine with vault-backed loop lineage persistence.
4
+ * invokeLoopCompile (primary), invokeFeedback.
5
5
  * Circuit breaker prevents infinite stall loops.
6
6
  * EngineMetrics tracks silent-failure counters for observability.
7
7
  */
8
8
  import { randomUUID } from "node:crypto";
9
9
  import { getPolicy } from "./policy.js";
10
10
  import { FSBackend, readLineageMd } from "./backends/fs.js";
11
- import { AgentStatus, Mode, makeAnalysis, makeExecutionFeedback, makeLoopCompileRequest, makeLoopObjective, makeLoopRoundResult, makeSelfEvaluation, makeSessionState, makeTaskId, makeVaultConfig, SELF_EVAL_REGEX, } from "./protocol.js";
12
- import { routeTechnique } from "./builder.js";
11
+ import { AgentStatus, Mode, makeAnalysis, makeExecutionEvidence, makeExecutionFeedback, makeLoopCompileRequest, makeLoopObjective, makeLoopRoundResult, makeSelfEvaluation, makeSessionState, makeTaskId, makeVaultConfig, SELF_EVAL_REGEX, } from "./protocol.js";
13
12
  import { scoreQuality } from "./builder.js";
14
13
  import { compileLoop } from "./loop-compiler.js";
14
+ import { logEvent } from "./observability.js";
15
15
  // ═══════════════════════════════════════════════════════════════════════════
16
16
  // Self-Evaluation extraction (v1.1 — autonomous loop feedback)
17
17
  // ═══════════════════════════════════════════════════════════════════════════
18
18
  /** Extract a structured SelfEvaluation from agent output text.
19
- * Returns null if no valid self-evaluation block is found.
19
+ * Returns null if no valid self-eval block is found.
20
20
  * The agent is instructed to output JSON between the delimiters. */
21
21
  export function extractSelfEvaluation(text) {
22
22
  const match = text.match(SELF_EVAL_REGEX);
@@ -33,17 +33,109 @@ export function extractSelfEvaluation(text) {
33
33
  return null;
34
34
  if (typeof raw.should_continue !== "boolean")
35
35
  return null;
36
- return makeSelfEvaluation({
37
- success: raw.success,
38
- output_summary: raw.output_summary,
39
- constraint_violations: raw.constraint_violations,
40
- should_continue: raw.should_continue,
41
- });
36
+ return buildSelfEvaluation(raw);
42
37
  }
43
38
  catch {
44
39
  return null;
45
40
  }
46
41
  }
42
+ /** Build a SelfEvaluation from a parsed JSON object.
43
+ * Lenient parsing: missing optional fields get sensible defaults.
44
+ * Used by extractSelfEvaluation() (regex path) and MCP tool handler
45
+ * (structured evaluation parameter path). */
46
+ export function buildSelfEvaluation(raw) {
47
+ // P4: Parse execution evidence from raw JSON
48
+ let executionEvidence;
49
+ const rawEvidence = raw.execution_evidence;
50
+ if (rawEvidence && typeof rawEvidence === "object") {
51
+ const testResults = rawEvidence.test_results;
52
+ executionEvidence = makeExecutionEvidence({
53
+ files_changed: Array.isArray(rawEvidence.files_changed)
54
+ ? rawEvidence.files_changed.filter((v) => typeof v === "string")
55
+ : [],
56
+ test_results: testResults && typeof testResults.passed === "number"
57
+ ? {
58
+ passed: testResults.passed,
59
+ failed: testResults.failed ?? 0,
60
+ skipped: testResults.skipped ?? 0,
61
+ }
62
+ : null,
63
+ success_criteria_met: Array.isArray(rawEvidence.success_criteria_met)
64
+ ? rawEvidence.success_criteria_met.filter((v) => typeof v === "string")
65
+ : [],
66
+ success_criteria_remaining: Array.isArray(rawEvidence.success_criteria_remaining)
67
+ ? rawEvidence.success_criteria_remaining.filter((v) => typeof v === "string")
68
+ : [],
69
+ progress_estimate: typeof rawEvidence.progress_estimate === "number"
70
+ ? Math.max(0, Math.min(1, rawEvidence.progress_estimate))
71
+ : 0.0,
72
+ });
73
+ }
74
+ // P5: Parse corrections
75
+ const retractedConstraints = Array.isArray(raw.retracted_constraints)
76
+ ? raw.retracted_constraints.filter((v) => typeof v === "string")
77
+ : [];
78
+ const revisedCriteria = Array.isArray(raw.revised_success_criteria)
79
+ ? raw.revised_success_criteria
80
+ .filter((v) => typeof v === "object" && v !== null &&
81
+ typeof v.old === "string" &&
82
+ typeof v.new === "string")
83
+ .map((v) => {
84
+ const r = v;
85
+ return { old: r.old, new: r.new };
86
+ })
87
+ : [];
88
+ const wrongAssumptions = Array.isArray(raw.wrong_assumptions)
89
+ ? raw.wrong_assumptions.filter((v) => typeof v === "string")
90
+ : [];
91
+ // Multi-agent: Parse worker delegation results
92
+ const workerResults = Array.isArray(raw.worker_results)
93
+ ? raw.worker_results
94
+ .filter((v) => typeof v === "object" && v !== null &&
95
+ typeof v.agentId === "string" &&
96
+ typeof v.subTask === "string" &&
97
+ typeof v.resultSummary === "string")
98
+ .map((v) => {
99
+ const w = v;
100
+ return {
101
+ agentId: w.agentId,
102
+ subAgentType: typeof w.subAgentType === "string" ? w.subAgentType : "general-purpose",
103
+ subTask: w.subTask,
104
+ resultSummary: w.resultSummary,
105
+ success: typeof w.success === "boolean" ? w.success : false,
106
+ discoveredConstraints: Array.isArray(w.discoveredConstraints)
107
+ ? w.discoveredConstraints.filter((c) => typeof c === "string")
108
+ : [],
109
+ };
110
+ })
111
+ : [];
112
+ return makeSelfEvaluation({
113
+ success: typeof raw.success === "boolean" ? raw.success : false,
114
+ output_summary: typeof raw.output_summary === "string" ? raw.output_summary : "",
115
+ constraint_violations: Array.isArray(raw.constraint_violations)
116
+ ? raw.constraint_violations.filter((v) => typeof v === "string")
117
+ : [],
118
+ should_continue: typeof raw.should_continue === "boolean" ? raw.should_continue : true,
119
+ // P0–P2: Optional evolution fields
120
+ discovered_constraints: Array.isArray(raw.discovered_constraints)
121
+ ? raw.discovered_constraints.filter((v) => typeof v === "string")
122
+ : [],
123
+ objective_refinement: typeof raw.objective_refinement === "string"
124
+ ? raw.objective_refinement
125
+ : "",
126
+ emerged_subtasks: Array.isArray(raw.emerged_subtasks)
127
+ ? raw.emerged_subtasks.filter((v) => typeof v === "string")
128
+ : [],
129
+ // P4: Execution evidence
130
+ execution_evidence: executionEvidence,
131
+ // P5: Self-correction
132
+ retracted_constraints: retractedConstraints,
133
+ revised_success_criteria: revisedCriteria,
134
+ wrong_assumptions: wrongAssumptions,
135
+ // Multi-agent: Worker delegation results
136
+ worker_results: workerResults,
137
+ });
138
+ }
47
139
  /** Fallback heuristic when structured self-eval extraction fails.
48
140
  * Scans agent output for completion and error signals.
49
141
  * Returns a low-confidence SelfEvaluation — the autonomous runner
@@ -99,6 +191,17 @@ export class LoopForgeEngine {
99
191
  }
100
192
  return this.backend;
101
193
  }
194
+ /** Public accessor for the vault backend — used by runtime/verification gate. */
195
+ getBackend() {
196
+ return this.resolveBackend();
197
+ }
198
+ /** Expose engine health counters for observability (MCP status, logging). */
199
+ getMetrics() {
200
+ if (this.metrics === null) {
201
+ this.metrics = makeEngineMetrics();
202
+ }
203
+ return this.metrics;
204
+ }
102
205
  ensureInit(request) {
103
206
  if (this.state === null) {
104
207
  this.state = makeSessionState(request.task_id || makeTaskId(request.task));
@@ -148,7 +251,7 @@ export class LoopForgeEngine {
148
251
  : "partial",
149
252
  quality_score: signal.quality_score ?? 0,
150
253
  constraint_compliance: {
151
- all_hard_constraints_met: !signal.violations,
254
+ all_hard_constraints_met: !Array.isArray(signal.violations) || signal.violations.length === 0,
152
255
  violations: signal.violations ?? [],
153
256
  },
154
257
  output_summary: signal.task_type ?? "",
@@ -165,6 +268,7 @@ export class LoopForgeEngine {
165
268
  catch {
166
269
  if (this.metrics)
167
270
  this.metrics.vaultWriteErrors++;
271
+ logEvent("vault_write_error", { error: "feedback_entry_build" });
168
272
  }
169
273
  }
170
274
  if (entries.length > 0) {
@@ -174,6 +278,7 @@ export class LoopForgeEngine {
174
278
  catch {
175
279
  if (this.metrics)
176
280
  this.metrics.vaultWriteErrors += entries.length;
281
+ logEvent("vault_write_error", { error: "feedback_append_entries", count: entries.length });
177
282
  return 0;
178
283
  }
179
284
  }
@@ -234,6 +339,7 @@ export class LoopForgeEngine {
234
339
  catch {
235
340
  if (this.metrics)
236
341
  this.metrics.vaultWriteErrors++;
342
+ logEvent("vault_write_error", { error: "persist_lineage_json" });
237
343
  }
238
344
  // 2. Markdown write (secondary, non-blocking)
239
345
  try {
@@ -254,10 +360,51 @@ export class LoopForgeEngine {
254
360
  catch {
255
361
  if (this.metrics)
256
362
  this.metrics.vaultWriteErrors++;
363
+ logEvent("vault_write_error", { error: "persist_lineage_md" });
257
364
  }
258
365
  return vaultOk;
259
366
  }
260
367
  // ═══════════════════════════════════════════════════════════════════════
368
+ // Delegation journal (v1.9 — AgentTool mode)
369
+ // ═══════════════════════════════════════════════════════════════════════
370
+ /** Record sub-agent delegations for this round into the vault.
371
+ * Written as a lightweight journal entry so the main agent's rolling
372
+ * summary can reference delegation history in subsequent rounds. */
373
+ recordDelegation(loopId, round, entries) {
374
+ if (!entries.length)
375
+ return;
376
+ const entry = {
377
+ id: randomUUID(),
378
+ task_id: `loop:${loopId}:r${round}:delegations`,
379
+ version_tag: "v1",
380
+ is_active: true,
381
+ timestamp: new Date().toISOString().replace(/\.\d+Z$/, ""),
382
+ user_intent: `Delegation journal — round ${round}`,
383
+ task_type: "delegation_journal",
384
+ loop_id: loopId,
385
+ loop_lineage: {
386
+ round,
387
+ delegations: entries.map((e) => ({
388
+ index: e.index,
389
+ agentId: e.agentId,
390
+ subAgentType: e.subAgentType,
391
+ subTask: e.subTask,
392
+ resultSummary: e.resultSummary,
393
+ success: e.success,
394
+ discoveredConstraints: e.discoveredConstraints,
395
+ })),
396
+ },
397
+ };
398
+ try {
399
+ this.resolveBackend().appendEntry(entry);
400
+ }
401
+ catch {
402
+ if (this.metrics)
403
+ this.metrics.vaultWriteErrors++;
404
+ logEvent("vault_write_error", { error: "record_delegation" });
405
+ }
406
+ }
407
+ // ═══════════════════════════════════════════════════════════════════════
261
408
  // Hydrate loop context from vault
262
409
  // ═══════════════════════════════════════════════════════════════════════
263
410
  hydrateLoopContext(loopId) {
@@ -309,6 +456,10 @@ export class LoopForgeEngine {
309
456
  entry.constraint_violations =
310
457
  lineage.constraint_violations ?? [];
311
458
  }
459
+ // v1.7: Ensure quality_score is always populated for failure lineage weighting
460
+ if (entry.quality_score === undefined || entry.quality_score === null) {
461
+ entry.quality_score = lineage.quality_score ?? 0;
462
+ }
312
463
  }
313
464
  // Enrich: attach full prompt text from Markdown for L0 cache reuse
314
465
  for (const entry of finalResults) {
@@ -328,37 +479,6 @@ export class LoopForgeEngine {
328
479
  return { results: finalResults, global_entries: [] };
329
480
  }
330
481
  // ═══════════════════════════════════════════════════════════════════════
331
- // Build (internal)
332
- // ═══════════════════════════════════════════════════════════════════════
333
- invokeBuild(request, _hydrateResults) {
334
- this.ensureInit(request);
335
- this.lastTask = request.task;
336
- const analysis = routeTechnique(request.task);
337
- const technique = analysis.technique;
338
- const rationale = analysis.rationale;
339
- const promptSections = [
340
- "## LoopForge Build",
341
- `**Technique**: ${technique}`,
342
- `**Rationale**: ${rationale}`,
343
- "",
344
- "### Task",
345
- request.task,
346
- "",
347
- "### Instructions",
348
- `Apply the **${technique}** technique to complete the task above.`,
349
- "Respect all hard constraints and provide verifiable output.",
350
- ];
351
- return {
352
- status: AgentStatus.OK,
353
- response: {
354
- status: AgentStatus.OK,
355
- prompt: promptSections.join("\n"),
356
- analysis,
357
- error: null,
358
- },
359
- };
360
- }
361
- // ═══════════════════════════════════════════════════════════════════════
362
482
  // Feedback (public)
363
483
  // ═══════════════════════════════════════════════════════════════════════
364
484
  invokeFeedback(request, _hydrateResults) {
@@ -420,6 +540,12 @@ export class LoopForgeEngine {
420
540
  else {
421
541
  this.state.circuit_breaker_count = 0;
422
542
  }
543
+ logEvent("round_complete", {
544
+ technique: "feedback",
545
+ quality,
546
+ loopId: loopId ?? "unknown",
547
+ round: loopRound ?? this.state.call_count,
548
+ });
423
549
  return {
424
550
  status: AgentStatus.OK,
425
551
  response: {
@@ -440,6 +566,8 @@ export class LoopForgeEngine {
440
566
  // ═══════════════════════════════════════════════════════════════════════
441
567
  /** Record self-evaluation from agent output without human intervention.
442
568
  * Converts SelfEvaluation → ExecutionFeedback → vault persistence.
569
+ * P0–P2: Also persists discovered_constraints, objective_refinement,
570
+ * and emerged_subtasks for the compiler to consume next round.
443
571
  * Call this BEFORE invokeLoopCompile for the next round so that
444
572
  * hydrateLoopContext picks up the latest quality scores. */
445
573
  autoFeedback(selfEval, loopId, round, task) {
@@ -456,6 +584,13 @@ export class LoopForgeEngine {
456
584
  manual_fixes_needed: fb.manual_fixes_needed,
457
585
  });
458
586
  const taskId = `loop:${loopId}:r${round}:feedback`;
587
+ // Multi-agent: Merge sub-agent discovered constraints into the main constraint flow
588
+ const subDiscovered = (selfEval.worker_results ?? [])
589
+ .flatMap((w) => w.discoveredConstraints ?? [])
590
+ .filter((c) => c.length > 0);
591
+ const mergedDiscovered = [
592
+ ...new Set([...(selfEval.discovered_constraints ?? []), ...subDiscovered]),
593
+ ];
459
594
  const signal = {
460
595
  task_id: taskId,
461
596
  task_type: task.slice(0, 80),
@@ -465,9 +600,34 @@ export class LoopForgeEngine {
465
600
  manual_fixes: "",
466
601
  loop_id: loopId,
467
602
  round,
603
+ // P0–P2: Evolution fields
604
+ discovered_constraints: mergedDiscovered,
605
+ objective_refinement: selfEval.objective_refinement ?? "",
606
+ emerged_subtasks: selfEval.emerged_subtasks ?? [],
607
+ // P4: Execution evidence
608
+ execution_evidence: selfEval.execution_evidence ?? null,
609
+ // P5: Self-correction
610
+ retracted_constraints: selfEval.retracted_constraints ?? [],
611
+ revised_success_criteria: selfEval.revised_success_criteria ?? [],
612
+ wrong_assumptions: selfEval.wrong_assumptions ?? [],
613
+ // Multi-agent: Worker delegation results
614
+ worker_results: selfEval.worker_results ?? [],
468
615
  };
469
616
  this.persistFeedbackToVault(signal);
470
617
  this.flushFeedbackBuffer();
618
+ // Multi-agent: Auto-record delegation journal from worker_results
619
+ if (selfEval.worker_results && selfEval.worker_results.length > 0) {
620
+ const entries = selfEval.worker_results.map((w, i) => ({
621
+ index: i + 1,
622
+ agentId: w.agentId,
623
+ subAgentType: w.subAgentType,
624
+ subTask: w.subTask,
625
+ resultSummary: w.resultSummary,
626
+ success: w.success,
627
+ discoveredConstraints: w.discoveredConstraints ?? [],
628
+ }));
629
+ this.recordDelegation(loopId, round, entries);
630
+ }
471
631
  // Update state
472
632
  this.state.call_count++;
473
633
  this.state.quality_trend.push(quality);
@@ -481,6 +641,12 @@ export class LoopForgeEngine {
481
641
  else {
482
642
  this.state.circuit_breaker_count = 0;
483
643
  }
644
+ logEvent("round_complete", {
645
+ loopId,
646
+ round,
647
+ quality,
648
+ technique: this.state?.last_technique ?? "feedback",
649
+ });
484
650
  return quality;
485
651
  }
486
652
  // ═══════════════════════════════════════════════════════════════════════
@@ -503,12 +669,47 @@ export class LoopForgeEngine {
503
669
  new_since_last_round: extras.new_since_last_round ?? "",
504
670
  force_level: extras.force_level ?? "auto",
505
671
  health_check_interval: extras.health_check_interval ?? 1,
672
+ external_context: extras.external_context ?? "",
506
673
  });
507
674
  // Convert last_round_result if present
508
675
  const lastRR = extras.last_round_result;
509
676
  if (lastRR) {
510
677
  if (typeof lastRR === "object" && !Array.isArray(lastRR)) {
511
678
  const rr = lastRR;
679
+ // Parse P4 execution evidence
680
+ let executionEvidence;
681
+ const rawEvidence = rr.execution_evidence;
682
+ if (rawEvidence && typeof rawEvidence === "object") {
683
+ const testResults = rawEvidence.test_results;
684
+ executionEvidence = makeExecutionEvidence({
685
+ files_changed: Array.isArray(rawEvidence.files_changed)
686
+ ? rawEvidence.files_changed.filter((v) => typeof v === "string")
687
+ : [],
688
+ test_results: testResults && typeof testResults.passed === "number"
689
+ ? {
690
+ passed: testResults.passed,
691
+ failed: testResults.failed ?? 0,
692
+ skipped: testResults.skipped ?? 0,
693
+ }
694
+ : null,
695
+ success_criteria_met: Array.isArray(rawEvidence.success_criteria_met)
696
+ ? rawEvidence.success_criteria_met.filter((v) => typeof v === "string")
697
+ : [],
698
+ success_criteria_remaining: Array.isArray(rawEvidence.success_criteria_remaining)
699
+ ? rawEvidence.success_criteria_remaining.filter((v) => typeof v === "string")
700
+ : [],
701
+ progress_estimate: typeof rawEvidence.progress_estimate === "number"
702
+ ? Math.max(0, Math.min(1, rawEvidence.progress_estimate))
703
+ : 0.0,
704
+ });
705
+ }
706
+ // Parse P5 revised_success_criteria
707
+ const revisedCriteria = Array.isArray(rr.revised_success_criteria)
708
+ ? rr.revised_success_criteria
709
+ .filter((v) => typeof v === "object" && v !== null &&
710
+ typeof v.old === "string" && typeof v.new === "string")
711
+ .map((v) => ({ old: v.old, new: v.new }))
712
+ : [];
512
713
  lcr.last_round_result = makeLoopRoundResult({
513
714
  round: rr.round ?? 0,
514
715
  success: rr.success ?? false,
@@ -516,6 +717,41 @@ export class LoopForgeEngine {
516
717
  constraint_violations: rr.constraint_violations ?? [],
517
718
  manual_fixes_needed: rr.manual_fixes_needed ?? "",
518
719
  quality_score: rr.quality_score ?? 0,
720
+ // P0–P2: Cognitive evolution fields
721
+ discovered_constraints: Array.isArray(rr.discovered_constraints)
722
+ ? rr.discovered_constraints.filter((v) => typeof v === "string")
723
+ : [],
724
+ objective_refinement: typeof rr.objective_refinement === "string"
725
+ ? rr.objective_refinement
726
+ : "",
727
+ emerged_subtasks: Array.isArray(rr.emerged_subtasks)
728
+ ? rr.emerged_subtasks.filter((v) => typeof v === "string")
729
+ : [],
730
+ // P4: Execution evidence
731
+ execution_evidence: executionEvidence,
732
+ // P5: Self-correction
733
+ retracted_constraints: Array.isArray(rr.retracted_constraints)
734
+ ? rr.retracted_constraints.filter((v) => typeof v === "string")
735
+ : [],
736
+ revised_success_criteria: revisedCriteria,
737
+ wrong_assumptions: Array.isArray(rr.wrong_assumptions)
738
+ ? rr.wrong_assumptions.filter((v) => typeof v === "string")
739
+ : [],
740
+ // Multi-agent: Worker delegation results
741
+ worker_results: Array.isArray(rr.worker_results)
742
+ ? rr.worker_results
743
+ .filter((v) => typeof v?.agentId === "string" && typeof v?.subTask === "string")
744
+ .map((v) => ({
745
+ agentId: v.agentId,
746
+ subAgentType: typeof v.subAgentType === "string" ? v.subAgentType : "general-purpose",
747
+ subTask: v.subTask,
748
+ resultSummary: typeof v.resultSummary === "string" ? v.resultSummary : "",
749
+ success: typeof v.success === "boolean" ? v.success : false,
750
+ discoveredConstraints: Array.isArray(v.discoveredConstraints)
751
+ ? v.discoveredConstraints
752
+ : [],
753
+ }))
754
+ : [],
519
755
  });
520
756
  }
521
757
  }
@@ -568,6 +804,22 @@ export class LoopForgeEngine {
568
804
  promptLines.push(`- ⚠️ ${w}`);
569
805
  }
570
806
  }
807
+ // Verification gate flags (v1.6) — injected after compiler warnings
808
+ const verificationFlags = extras.verification_flags ?? [];
809
+ if (verificationFlags.length) {
810
+ promptLines.push("");
811
+ promptLines.push("### Verification Gate");
812
+ for (const f of verificationFlags) {
813
+ const icon = f.severity === "error" ? "🚫" : "⚠️";
814
+ promptLines.push(`- ${icon} [${f.check}] ${f.detail}`);
815
+ }
816
+ const hasError = verificationFlags.some((f) => f.severity === "error");
817
+ if (hasError) {
818
+ promptLines.push("");
819
+ promptLines.push("**Gate Verdict: CONTRADICTED** — this round's quality score has been excluded " +
820
+ "from the quality trend. Address each 🚫 flag explicitly in your next response.");
821
+ }
822
+ }
571
823
  if (response.loop_health) {
572
824
  const h = response.loop_health;
573
825
  promptLines.push("");
@@ -617,67 +869,6 @@ export class LoopForgeEngine {
617
869
  };
618
870
  }
619
871
  // ═══════════════════════════════════════════════════════════════════════
620
- // Review handler
621
- // ═══════════════════════════════════════════════════════════════════════
622
- handleReview(request, hydrateResults) {
623
- if (!hydrateResults) {
624
- return {
625
- status: AgentStatus.ERROR,
626
- response: {
627
- status: AgentStatus.ERROR,
628
- prompt: null,
629
- analysis: null,
630
- error: "Review mode requires hydrate_results (prompt to review).",
631
- },
632
- };
633
- }
634
- const results = hydrateResults.results ?? [];
635
- if (!results.length) {
636
- return {
637
- status: AgentStatus.ERROR,
638
- response: {
639
- status: AgentStatus.ERROR,
640
- prompt: null,
641
- analysis: null,
642
- error: "No matching prompt found in vault to review.",
643
- },
644
- };
645
- }
646
- const promptData = results[0];
647
- const fullText = promptData.full_prompt ?? "";
648
- const issues = [];
649
- // Structural checks
650
- const requiredSections = [
651
- "角色", "任务", "输入", "输出格式", "硬约束", "生成要求",
652
- ];
653
- for (const section of requiredSections) {
654
- if (!fullText.includes(section)) {
655
- issues.push(`Missing section: ${section}`);
656
- }
657
- }
658
- // Constraint check
659
- const globalEntries = hydrateResults.global_entries ?? [];
660
- for (const entry of globalEntries) {
661
- for (const c of entry.hard_constraints_added ?? []) {
662
- if (!fullText.includes(c)) {
663
- issues.push(`GLOBAL constraint not reflected: ${c}`);
664
- }
665
- }
666
- }
667
- const reviewReport = issues.length
668
- ? issues.map((i) => `- ${i}`).join("\n")
669
- : "All checks passed.";
670
- return {
671
- status: AgentStatus.OK,
672
- response: {
673
- status: AgentStatus.OK,
674
- prompt: `## Review Report\n\n${reviewReport}\n\n---\n\n${fullText}`,
675
- analysis: null,
676
- error: null,
677
- },
678
- };
679
- }
680
- // ═══════════════════════════════════════════════════════════════════════
681
872
  // Circuit breaker
682
873
  // ═══════════════════════════════════════════════════════════════════════
683
874
  shouldBreak() {
@@ -688,7 +879,14 @@ export class LoopForgeEngine {
688
879
  if (this.state.quality_trend.length < maxCB)
689
880
  return false;
690
881
  const recent = this.state.quality_trend.slice(-maxCB);
691
- return recent.every((val, i) => i === 0 || recent[i - 1] >= val);
882
+ const isFlat = recent.every((val, i) => i === 0 || recent[i - 1] >= val);
883
+ if (isFlat) {
884
+ logEvent("circuit_breaker", {
885
+ trend: recent,
886
+ totalRounds: this.state.quality_trend.length,
887
+ });
888
+ }
889
+ return isFlat;
692
890
  }
693
891
  }
694
892
  // ═══════════════════════════════════════════════════════════════════════════