loopforge 3.0.0 → 3.6.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 (194) hide show
  1. package/README.md +126 -143
  2. package/dist/audit.d.ts +50 -0
  3. package/dist/audit.d.ts.map +1 -0
  4. package/dist/audit.js +131 -0
  5. package/dist/audit.js.map +1 -0
  6. package/dist/canonical-state.d.ts +98 -23
  7. package/dist/canonical-state.d.ts.map +1 -1
  8. package/dist/canonical-state.js +257 -181
  9. package/dist/canonical-state.js.map +1 -1
  10. package/dist/cli.d.ts.map +1 -1
  11. package/dist/cli.js +40 -33
  12. package/dist/cli.js.map +1 -1
  13. package/dist/cognitive-facts.d.ts +17 -0
  14. package/dist/cognitive-facts.d.ts.map +1 -0
  15. package/dist/cognitive-facts.js +99 -0
  16. package/dist/cognitive-facts.js.map +1 -0
  17. package/dist/cognitive-governance.d.ts +25 -33
  18. package/dist/cognitive-governance.d.ts.map +1 -1
  19. package/dist/cognitive-governance.js +38 -138
  20. package/dist/cognitive-governance.js.map +1 -1
  21. package/dist/committed-round.d.ts +59 -0
  22. package/dist/committed-round.d.ts.map +1 -0
  23. package/dist/committed-round.js +222 -0
  24. package/dist/committed-round.js.map +1 -0
  25. package/dist/enforcement-gate.d.ts +78 -0
  26. package/dist/enforcement-gate.d.ts.map +1 -0
  27. package/dist/enforcement-gate.js +1109 -0
  28. package/dist/enforcement-gate.js.map +1 -0
  29. package/dist/engine.d.ts +111 -0
  30. package/dist/engine.d.ts.map +1 -0
  31. package/dist/engine.js +887 -0
  32. package/dist/engine.js.map +1 -0
  33. package/dist/evidence-claims.d.ts +50 -0
  34. package/dist/evidence-claims.d.ts.map +1 -0
  35. package/dist/evidence-claims.js +116 -0
  36. package/dist/evidence-claims.js.map +1 -0
  37. package/dist/evidence-provider.d.ts +50 -23
  38. package/dist/evidence-provider.d.ts.map +1 -1
  39. package/dist/evidence-provider.js +167 -50
  40. package/dist/evidence-provider.js.map +1 -1
  41. package/dist/generate-schema.js +1 -84
  42. package/dist/generate-schema.js.map +1 -1
  43. package/dist/index.d.ts +19 -22
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +16 -15
  46. package/dist/index.js.map +1 -1
  47. package/dist/loop-compiler.d.ts +58 -3
  48. package/dist/loop-compiler.d.ts.map +1 -1
  49. package/dist/loop-compiler.js +841 -304
  50. package/dist/loop-compiler.js.map +1 -1
  51. package/dist/loop-extras-parser.d.ts +2 -2
  52. package/dist/loop-extras-parser.d.ts.map +1 -1
  53. package/dist/loop-extras-parser.js +2 -2
  54. package/dist/loop-extras-parser.js.map +1 -1
  55. package/dist/loop-projection.d.ts +6 -0
  56. package/dist/loop-projection.d.ts.map +1 -0
  57. package/dist/loop-projection.js +26 -0
  58. package/dist/loop-projection.js.map +1 -0
  59. package/dist/loop-store.d.ts +115 -24
  60. package/dist/loop-store.d.ts.map +1 -1
  61. package/dist/loop-store.js +365 -218
  62. package/dist/loop-store.js.map +1 -1
  63. package/dist/mcp/round-lifecycle.d.ts +214 -0
  64. package/dist/mcp/round-lifecycle.d.ts.map +1 -0
  65. package/dist/mcp/round-lifecycle.js +935 -0
  66. package/dist/mcp/round-lifecycle.js.map +1 -0
  67. package/dist/mcp/server.d.ts +2 -1
  68. package/dist/mcp/server.d.ts.map +1 -1
  69. package/dist/mcp/server.js +29 -43
  70. package/dist/mcp/server.js.map +1 -1
  71. package/dist/mcp/session.d.ts +64 -101
  72. package/dist/mcp/session.d.ts.map +1 -1
  73. package/dist/mcp/session.js +433 -1110
  74. package/dist/mcp/session.js.map +1 -1
  75. package/dist/mcp/tools.d.ts +20 -11
  76. package/dist/mcp/tools.d.ts.map +1 -1
  77. package/dist/mcp/tools.js +587 -413
  78. package/dist/mcp/tools.js.map +1 -1
  79. package/dist/policy-metrics.d.ts +12 -24
  80. package/dist/policy-metrics.d.ts.map +1 -1
  81. package/dist/policy-metrics.js +78 -184
  82. package/dist/policy-metrics.js.map +1 -1
  83. package/dist/policy.d.ts +61 -50
  84. package/dist/policy.d.ts.map +1 -1
  85. package/dist/policy.js +48 -146
  86. package/dist/policy.js.map +1 -1
  87. package/dist/prompt-assembler.d.ts +37 -6
  88. package/dist/prompt-assembler.d.ts.map +1 -1
  89. package/dist/prompt-assembler.js +487 -278
  90. package/dist/prompt-assembler.js.map +1 -1
  91. package/dist/prompt-policy.d.ts +1 -8
  92. package/dist/prompt-policy.d.ts.map +1 -1
  93. package/dist/prompt-policy.js +8 -26
  94. package/dist/prompt-policy.js.map +1 -1
  95. package/dist/protocol.d.ts +398 -236
  96. package/dist/protocol.d.ts.map +1 -1
  97. package/dist/protocol.js +147 -17
  98. package/dist/protocol.js.map +1 -1
  99. package/dist/replay.d.ts +5 -13
  100. package/dist/replay.d.ts.map +1 -1
  101. package/dist/replay.js +32 -69
  102. package/dist/replay.js.map +1 -1
  103. package/dist/round-contract.d.ts +64 -0
  104. package/dist/round-contract.d.ts.map +1 -0
  105. package/dist/round-contract.js +80 -0
  106. package/dist/round-contract.js.map +1 -0
  107. package/dist/round-coordinator.d.ts +23 -25
  108. package/dist/round-coordinator.d.ts.map +1 -1
  109. package/dist/round-coordinator.js +103 -92
  110. package/dist/round-coordinator.js.map +1 -1
  111. package/dist/round-driver.d.ts +13 -13
  112. package/dist/round-driver.d.ts.map +1 -1
  113. package/dist/round-driver.js +25 -14
  114. package/dist/round-driver.js.map +1 -1
  115. package/dist/round-transaction.d.ts +14 -18
  116. package/dist/round-transaction.d.ts.map +1 -1
  117. package/dist/round-transaction.js +37 -47
  118. package/dist/round-transaction.js.map +1 -1
  119. package/dist/self-eval.d.ts +48 -0
  120. package/dist/self-eval.d.ts.map +1 -0
  121. package/dist/self-eval.js +248 -0
  122. package/dist/self-eval.js.map +1 -0
  123. package/dist/storage.d.ts +9 -4
  124. package/dist/storage.d.ts.map +1 -1
  125. package/dist/storage.js +18 -6
  126. package/dist/storage.js.map +1 -1
  127. package/dist/token-utils.d.ts +15 -3
  128. package/dist/token-utils.d.ts.map +1 -1
  129. package/dist/token-utils.js +24 -5
  130. package/dist/token-utils.js.map +1 -1
  131. package/dist/verification-gate.d.ts +149 -0
  132. package/dist/verification-gate.d.ts.map +1 -0
  133. package/dist/verification-gate.js +1343 -0
  134. package/dist/verification-gate.js.map +1 -0
  135. package/dist/workspace.d.ts +29 -0
  136. package/dist/workspace.d.ts.map +1 -0
  137. package/dist/workspace.js +61 -0
  138. package/dist/workspace.js.map +1 -0
  139. package/loop_policy.json +37 -16
  140. package/package.json +5 -10
  141. package/skills/perception/SKILL.md +184 -202
  142. package/dist/canonical-event-writer.d.ts +0 -20
  143. package/dist/canonical-event-writer.d.ts.map +0 -1
  144. package/dist/canonical-event-writer.js +0 -131
  145. package/dist/canonical-event-writer.js.map +0 -1
  146. package/dist/compiler-subgoals.d.ts +0 -7
  147. package/dist/compiler-subgoals.d.ts.map +0 -1
  148. package/dist/compiler-subgoals.js +0 -93
  149. package/dist/compiler-subgoals.js.map +0 -1
  150. package/dist/derive-id.d.ts +0 -12
  151. package/dist/derive-id.d.ts.map +0 -1
  152. package/dist/derive-id.js +0 -31
  153. package/dist/derive-id.js.map +0 -1
  154. package/dist/mcp/session-projection.d.ts +0 -5
  155. package/dist/mcp/session-projection.d.ts.map +0 -1
  156. package/dist/mcp/session-projection.js +0 -18
  157. package/dist/mcp/session-projection.js.map +0 -1
  158. package/dist/mcp/session-queue.d.ts +0 -3
  159. package/dist/mcp/session-queue.d.ts.map +0 -1
  160. package/dist/mcp/session-queue.js +0 -18
  161. package/dist/mcp/session-queue.js.map +0 -1
  162. package/dist/mcp/session-types.d.ts +0 -72
  163. package/dist/mcp/session-types.d.ts.map +0 -1
  164. package/dist/mcp/session-types.js +0 -2
  165. package/dist/mcp/session-types.js.map +0 -1
  166. package/dist/round-commit-store.d.ts +0 -12
  167. package/dist/round-commit-store.d.ts.map +0 -1
  168. package/dist/round-commit-store.js +0 -13
  169. package/dist/round-commit-store.js.map +0 -1
  170. package/dist/round-gate.d.ts +0 -31
  171. package/dist/round-gate.d.ts.map +0 -1
  172. package/dist/round-gate.js +0 -259
  173. package/dist/round-gate.js.map +0 -1
  174. package/dist/round-recovery.d.ts +0 -17
  175. package/dist/round-recovery.d.ts.map +0 -1
  176. package/dist/round-recovery.js +0 -117
  177. package/dist/round-recovery.js.map +0 -1
  178. package/dist/round-report.d.ts +0 -16
  179. package/dist/round-report.d.ts.map +0 -1
  180. package/dist/round-report.js +0 -401
  181. package/dist/round-report.js.map +0 -1
  182. package/dist/runtime-services.d.ts +0 -59
  183. package/dist/runtime-services.d.ts.map +0 -1
  184. package/dist/runtime-services.js +0 -460
  185. package/dist/runtime-services.js.map +0 -1
  186. package/dist/vault-format.d.ts +0 -4
  187. package/dist/vault-format.d.ts.map +0 -1
  188. package/dist/vault-format.js +0 -4
  189. package/dist/vault-format.js.map +0 -1
  190. package/dist/vault-types.d.ts +0 -24
  191. package/dist/vault-types.d.ts.map +0 -1
  192. package/dist/vault-types.js +0 -2
  193. package/dist/vault-types.js.map +0 -1
  194. package/loopforge-policy.json +0 -340
@@ -1 +1 @@
1
- {"version":3,"file":"canonical-state.d.ts","sourceRoot":"","sources":["../src/canonical-state.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"canonical-state.d.ts","sourceRoot":"","sources":["../src/canonical-state.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EACf,MAAM,EACN,kBAAkB,EAClB,mBAAmB,EACnB,gBAAgB,EAChB,aAAa,EACb,OAAO,EACP,gBAAgB,EACjB,MAAM,eAAe,CAAC;AAIvB,eAAO,MAAM,8BAA8B,EAAG,CAAU,CAAC;AAEzD;;;uEAGuE;AACvE,MAAM,WAAW,sBAAsB;IACrC,6EAA6E;IAC7E,KAAK,EAAE,MAAM,CAAC;IACd;0DACsD;IACtD,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB;8EAC0E;IAC1E,QAAQ,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAClC,wEAAwE;IACxE,eAAe,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CAC1C;AAED;;6EAE6E;AAC7E,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED;;;;2EAI2E;AAC3E,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,OAAO,8BAA8B,CAAC;IACrD,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,wEAAwE;IACxE,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,+CAA+C;IAC/C,kBAAkB,EAAE,cAAc,EAAE,CAAC;IACrC,qBAAqB,EAAE,MAAM,EAAE,CAAC;IAChC,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,iBAAiB,EAAE,gBAAgB,EAAE,CAAC;IACtC,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,6EAA6E;IAC7E,UAAU,EAAE,gBAAgB,EAAE,CAAC;IAC/B,kEAAkE;IAClE,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,4EAA4E;IAC5E,iBAAiB,EAAE,eAAe,EAAE,CAAC;IACrC,4EAA4E;IAC5E,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,8DAA8D;IAC9D,eAAe,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,6CAA6C;IAC7C,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE;QACR,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,WAAW,EAAE,MAAM,EAAE,CAAC;QACtB,iBAAiB,EAAE,MAAM,EAAE,CAAC;QAC5B,YAAY,EAAE,MAAM,EAAE,CAAC;QACvB,KAAK,EAAE;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAA;SAAE,GAAG,IAAI,CAAC;KACnE,CAAC;IACF;2EACuE;IACvE,UAAU,CAAC,EAAE,SAAS,EAAE,CAAC;IACzB;8EAC0E;IAC1E,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B;;;;mDAI+C;IAC/C,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAID,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAYtD;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,kBAAkB,GAAG,MAAM,CAEpE;AAwED;kEACkE;AAClE;;;;;;;;;;;mBAWmB;AACnB,wBAAgB,YAAY,CAAC,KAAK,EAAE,kBAAkB,GAAG,MAAM,EAAE,CAmDhE;AAED;;;mEAGmE;AACnE,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,CAcnE;AAMD;;;iCAGiC;AACjC,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKlD;AAED;;sEAEsE;AACtE,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,gBAAgB,GAAG,MAAM,CAKpE;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,kBAAkB,GAAG,MAAM,CAmN9E;AAED,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,kBAAkB,EAC3B,QAAQ,EAAE,mBAAmB,EAC7B,aAAa,EAAE,MAAM;AACrB;0EAC0E;AAC1E,OAAO,CAAC,EAAE;IACR,UAAU,CAAC,EAAE,SAAS,EAAE,CAAC;IACzB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B;;;4CAGwC;IACxC,aAAa,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;CACtC,GACA,kBAAkB,CAuFpB"}
@@ -4,11 +4,9 @@
4
4
  * consume the same value so they cannot silently drift apart.
5
5
  */
6
6
  import { createHash } from "node:crypto";
7
- import { deriveItemId, deriveTodoId } from "./derive-id.js";
8
- import { unique } from "./token-utils.js";
7
+ import { deriveItemId, STABLE_ID_RE, unique } from "./token-utils.js";
9
8
  import { getPolicy } from "./policy.js";
10
- import { deriveGate } from "./cognitive-governance.js";
11
- export const CANONICAL_STATE_FORMAT = "loopforge.state";
9
+ export const CANONICAL_STATE_SCHEMA_VERSION = 1;
12
10
  // unique() imported from token-utils.ts
13
11
  /** Deterministic JSON serialization used by state and prompt hashes. */
14
12
  export function stableStringify(value) {
@@ -48,8 +46,7 @@ function addListWithIds(lines, title, values, prefix) {
48
46
  if (!trimmed)
49
47
  continue;
50
48
  // If the value is already an ID, render it as-is
51
- const idRegex = /^(c|cr|sg)-[a-f0-9]{8}$/;
52
- if (idRegex.test(trimmed)) {
49
+ if (STABLE_ID_RE.test(trimmed)) {
53
50
  lines.push(`- [\`${trimmed}\`] ${trimmed}`);
54
51
  }
55
52
  else if (idEnabled) {
@@ -62,13 +59,144 @@ function addListWithIds(lines, title, values, prefix) {
62
59
  }
63
60
  lines.push("");
64
61
  }
62
+ /** v3.3.1: Identity-aware variant of addListWithIds for the merged active
63
+ * constraint set (hard/plan/criteria/discovered texts together). Each
64
+ * item's ID comes from constraint metadata — criteria texts keep their
65
+ * cr-XXXXXXXX identity, matching the Success Criteria section and the
66
+ * verification gate's ID-first criterion matching. Keeps the state file in
67
+ * the same identity namespace as the prompts (module contract: both
68
+ * renderers cannot drift apart). */
69
+ function addListWithIdentities(lines, title, values, metadata) {
70
+ if (values.length === 0)
71
+ return;
72
+ const idEnabled = getPolicy().evolution.constraint_id_enabled;
73
+ const idByText = new Map(metadata.map((meta) => [meta.text, meta.id]));
74
+ lines.push(`## ${title}`, "");
75
+ for (const value of values) {
76
+ const trimmed = value.trim();
77
+ if (!trimmed)
78
+ continue;
79
+ if (STABLE_ID_RE.test(trimmed)) {
80
+ lines.push(`- [\`${trimmed}\`] ${trimmed}`);
81
+ }
82
+ else if (idEnabled) {
83
+ const id = idByText.get(trimmed) ?? `c-${deriveItemId(trimmed)}`;
84
+ lines.push(`- [\`${id}\`] ${trimmed}`);
85
+ }
86
+ else {
87
+ lines.push(`- ${trimmed}`);
88
+ }
89
+ }
90
+ lines.push("");
91
+ }
65
92
  /** Human/Agent-readable materialized view. It is always reproducible from the
66
93
  * canonical state and is never consulted as transaction truth. */
94
+ /** v3.3: Forward-looking "roadmap" view derived from existing state — loop
95
+ * position, met/remaining criteria (with IDs), and sub-goal activity.
96
+ *
97
+ * Deliberately label-free: milestone labels/ranges are collapsed content in
98
+ * L1 (the diff-collapse invariant) and are rendered in full by Phase
99
+ * History everywhere else — the roadmap only reports "N rounds since the
100
+ * last milestone boundary", never the boundary's identity. Next action is
101
+ * likewise excluded: every level renders its own Next Action section.
102
+ * Shared by the state-file and prompt renderers so they cannot silently
103
+ * drift apart (module contract above). Returns [] — renders nothing —
104
+ * when the state carries none of these. Presentation only; never feeds
105
+ * enforcement. */
106
+ export function buildRoadmap(state) {
107
+ const lastMilestone = state.milestones.length > 0
108
+ ? state.milestones[state.milestones.length - 1]
109
+ : null;
110
+ const roundsSinceMilestone = lastMilestone !== null &&
111
+ lastMilestone.round_range.end <= state.round
112
+ ? state.round - lastMilestone.round_range.end
113
+ : null;
114
+ const met = state.criterionStatuses.filter((cs) => cs.status === "met").length;
115
+ const total = state.criterionStatuses.length;
116
+ const remaining = state.criterionStatuses.filter((cs) => cs.status === "remaining" || cs.status === "unknown");
117
+ const inProgress = state.subGoals.filter((sg) => sg.status === "in_progress").length;
118
+ const pending = state.subGoals.filter((sg) => sg.status === "pending").length;
119
+ // Bare position alone is not forward information — no milestone distance,
120
+ // criteria, remaining criteria, or sub-goal activity means nothing to
121
+ // look ahead to. Returns [] so empty states render no section at all.
122
+ if (roundsSinceMilestone === null && total === 0 &&
123
+ state.remainingCriteria.length === 0 && inProgress === 0 && pending === 0) {
124
+ return [];
125
+ }
126
+ const lines = [];
127
+ let position = `Position: round ${state.round}/${state.maxRounds}`;
128
+ if (roundsSinceMilestone !== null) {
129
+ position += ` · ${roundsSinceMilestone} round${roundsSinceMilestone === 1 ? "" : "s"} since the last milestone boundary`;
130
+ }
131
+ lines.push(`- ${position}`);
132
+ if (total > 0) {
133
+ lines.push(`- Criteria: ${met}/${total} met · ${remaining.length} remaining`);
134
+ }
135
+ else if (state.remainingCriteria.length > 0) {
136
+ lines.push(`- Criteria: ${state.remainingCriteria.length} remaining`);
137
+ }
138
+ if (remaining.length > 0) {
139
+ const idEnabled = getPolicy().evolution.constraint_id_enabled;
140
+ for (const cs of remaining.slice(0, 8)) {
141
+ const idTag = idEnabled ? ` [\`${cs.id}\`]` : "";
142
+ const icon = cs.status === "remaining" ? "⬜" : "❔";
143
+ lines.push(` - ${icon}${idTag} ${cs.text}`);
144
+ }
145
+ if (remaining.length > 8) {
146
+ lines.push(` - ... and ${remaining.length - 8} more`);
147
+ }
148
+ }
149
+ if (inProgress > 0 || pending > 0) {
150
+ lines.push(`- Sub-goals: ${inProgress} in progress · ${pending} pending`);
151
+ }
152
+ return lines;
153
+ }
154
+ /** v3.3/v3.4: Render the ACTIVE Round Contract as the Current Task section
155
+ * body — the single formatting source shared by prompts and the state
156
+ * file (module contract above). The original objective is NOT here: it
157
+ * lives in the Objective section. Empty arrays render no line. */
158
+ export function formatRoundContract(contract) {
159
+ const lines = [];
160
+ const heading = contract.work_item?.trim();
161
+ lines.push(`**${heading || "Round Contract"}**`);
162
+ for (const item of contract.done_when) {
163
+ lines.push(`- Done when: ${item}`);
164
+ }
165
+ for (const item of contract.verification_plan) {
166
+ lines.push(`- Verify via: ${item}`);
167
+ }
168
+ for (const item of contract.scope) {
169
+ lines.push(`- Scope: ${item}`);
170
+ }
171
+ return lines.join("\n");
172
+ }
173
+ // ═══════════════════════════════════════════════════════════════════════════
174
+ // v3.3.1: Shared presentation atoms
175
+ // ═══════════════════════════════════════════════════════════════════════════
176
+ /** The trust bar line ("██████░░░░ 60%"). The L2 Agent Trust section and the
177
+ * detailed L1 renderer used to carry private copies of this formula — shared
178
+ * here so a formatting change is made once (module contract: renderers must
179
+ * not silently drift apart). */
180
+ export function trustBarLine(score) {
181
+ const barLen = 10;
182
+ const filled = Math.round(score * barLen);
183
+ const bar = "█".repeat(filled) + "░".repeat(barLen - filled);
184
+ return `${bar} ${(score * 100).toFixed(0)}%`;
185
+ }
186
+ /** Milestone heading line ("**🏁 Round 7** (Rounds 3–7, 60%)"). Shared by the
187
+ * L2 Phase History and the detailed L1 renderer; the L1 copy previously
188
+ * rendered the range as "R3–R7", a format only this heading used. */
189
+ export function milestoneHeading(milestone) {
190
+ const kindIcon = milestone.kind === "agent_declared"
191
+ ? "🏁"
192
+ : milestone.kind === "criteria_milestone" ? "✅" : "📍";
193
+ return `**${kindIcon} ${milestone.label}** (Rounds ${milestone.round_range.start}–${milestone.round_range.end}, ${(milestone.progress_at_boundary * 100).toFixed(0)}%)`;
194
+ }
67
195
  export function renderCanonicalStateMarkdown(state) {
68
196
  const lines = [
69
197
  `# LoopForge State — ${state.loopId}`,
70
198
  "",
71
- `**Format**: ${state.format}`,
199
+ `**Schema**: ${state.schemaVersion}`,
72
200
  `**Round**: ${state.round}/${state.maxRounds}`,
73
201
  `**Goal ID**: ${state.goalId}`,
74
202
  "",
@@ -76,34 +204,21 @@ export function renderCanonicalStateMarkdown(state) {
76
204
  "",
77
205
  state.objective,
78
206
  "",
79
- "## Current Focus",
80
- "",
81
- state.currentFocus,
207
+ "## Current Task",
82
208
  "",
209
+ state.currentTask,
83
210
  ];
84
- if (state.todoProjection) {
85
- lines.push("## Active Todo Projection", "", state.todoProjection.action, "", `**Completion Signal**: ${state.todoProjection.completionSignal}`, "");
86
- }
87
- if (state.activeGate) {
88
- lines.push("## Active Gate", "");
89
- if (state.activeGate.kind === "user") {
90
- lines.push(`**Owner**: user`, `**Question**: ${state.activeGate.question}`);
91
- }
92
- else {
93
- lines.push(`**Owner**: agent`, `**Problem**: ${state.activeGate.problem}`);
94
- }
95
- lines.push("");
96
- }
97
211
  // v2.11: Success Criteria and Constraints render with stable IDs for exact agent matching
98
212
  addListWithIds(lines, "Success Criteria", state.successCriteria, "cr");
99
213
  addListWithIds(lines, "Hard Constraints", state.hardConstraints, "c");
100
- addListWithIds(lines, "Active Constraints", state.activeConstraints, "c");
214
+ addListWithIdentities(lines, "Active Constraints", state.activeConstraints, state.constraintMetadata);
101
215
  addList(lines, "Changes Since Last Round", state.changesSinceLastRound);
102
216
  if (state.progress.estimate !== null ||
103
217
  state.progress.criteriaMet.length > 0 ||
104
218
  state.progress.criteriaRemaining.length > 0 ||
105
219
  state.progress.filesChanged.length > 0 ||
106
- state.progress.tests !== null) {
220
+ state.progress.tests !== null ||
221
+ state.machineStatus !== undefined) {
107
222
  const total = state.progress.criteriaMet.length + state.progress.criteriaRemaining.length;
108
223
  lines.push("## Progress Dashboard", "");
109
224
  if (total > 0) {
@@ -111,6 +226,21 @@ export function renderCanonicalStateMarkdown(state) {
111
226
  }
112
227
  if (state.progress.estimate !== null) {
113
228
  lines.push(`**Estimated Completion**: ${(state.progress.estimate * 100).toFixed(0)}%`);
229
+ // v3.3: honest labeling — the estimate is the agent's own number.
230
+ lines.push("**Signal source**: self-reported estimate (unverified until machine-backed)");
231
+ }
232
+ // v3.3: machine side of the comparison — git motion over committed rounds
233
+ // (v3.6: the object always carries definite values — no "unavailable" arm).
234
+ if (state.machineStatus) {
235
+ const ms = state.machineStatus;
236
+ const motion = ms.gitMotion
237
+ ? `changes observed in ${ms.motionRounds}/${ms.windowRounds} recent committed rounds`
238
+ : `no git changes in the last ${ms.windowRounds} committed rounds`;
239
+ lines.push(`**Machine (git)**: ${motion}`);
240
+ }
241
+ if (state.criterionStatuses.length > 0) {
242
+ const metCount = state.criterionStatuses.filter((cs) => cs.status === "met").length;
243
+ lines.push(`**Machine (criteria)**: ${metCount}/${state.criterionStatuses.length} met across committed rounds`);
114
244
  }
115
245
  if (state.progress.tests) {
116
246
  lines.push(`**Tests**: ${state.progress.tests.passed} passed, ` +
@@ -123,13 +253,42 @@ export function renderCanonicalStateMarkdown(state) {
123
253
  }
124
254
  lines.push("");
125
255
  }
256
+ // v3.2: Goal → criteria → evidence vertical view. IDs follow
257
+ // constraint_id_enabled like every other ID-rendering path.
258
+ if (state.criterionStatuses.length > 0) {
259
+ const idEnabled = getPolicy().evolution.constraint_id_enabled;
260
+ lines.push("## Goal → Criteria", "");
261
+ for (const cs of state.criterionStatuses) {
262
+ const icon = cs.status === "met" ? "✅" : cs.status === "remaining" ? "⬜" : "❔";
263
+ const idTag = idEnabled ? ` [\`${cs.id}\`]` : "";
264
+ const met = cs.met_at_round !== undefined ? ` (met R${cs.met_at_round})` : "";
265
+ const related = cs.related_subgoal_ids.length > 0
266
+ ? ` (related: ${cs.related_subgoal_ids.join(", ")})`
267
+ : "";
268
+ lines.push(`- ${icon}${idTag} ${cs.text}${met}${related}`);
269
+ }
270
+ lines.push("");
271
+ }
126
272
  addListWithIds(lines, "Remaining", state.remainingCriteria, "cr");
127
273
  addList(lines, "Blockers", state.blockers);
128
274
  addList(lines, "Discoveries", state.discoveries);
129
275
  addList(lines, "Cross-Round Outcomes", state.rollingOutcomes);
130
276
  addList(lines, "Recurring Issues", state.recurringIssues);
131
277
  addList(lines, "Failed Patterns", state.failedPatterns);
132
- addList(lines, "Regression Obligations", state.regressionObligations.map((obligation) => `[${obligation.status}] ${obligation.description}`));
278
+ // v3.2: Lessons learned — repeated violations / verification failures.
279
+ if (state.lessons.length > 0) {
280
+ lines.push("## Lessons Learned", "");
281
+ for (const lesson of state.lessons.slice(0, 8)) {
282
+ const icon = lesson.kind === "constraint_violation"
283
+ ? "🚫"
284
+ : lesson.kind === "verification_error" ? "⚠️" : "ℹ️";
285
+ lines.push(`- ${icon} ${lesson.text} — ${lesson.count}× (R${lesson.rounds.join(", R")})`);
286
+ }
287
+ if (state.lessons.length > 8) {
288
+ lines.push(`- ... and ${state.lessons.length - 8} more`);
289
+ }
290
+ lines.push("");
291
+ }
133
292
  addList(lines, "Retired Constraints", state.retiredConstraints);
134
293
  if (state.inactiveConstraints.length > 0) {
135
294
  lines.push("## Inactive Constraints", "");
@@ -150,6 +309,17 @@ export function renderCanonicalStateMarkdown(state) {
150
309
  }
151
310
  lines.push("");
152
311
  }
312
+ if (state.nextAction) {
313
+ lines.push("## Next Action", "", state.nextAction, "");
314
+ }
315
+ // v3.3: Forward-looking roadmap — same buildRoadmap the prompts render.
316
+ const roadmap = buildRoadmap(state);
317
+ if (roadmap.length > 0) {
318
+ lines.push("## Roadmap", "");
319
+ for (const line of roadmap)
320
+ lines.push(line);
321
+ lines.push("");
322
+ }
153
323
  if (state.verificationFlags.length > 0) {
154
324
  lines.push("## Verification", "");
155
325
  for (const flag of state.verificationFlags) {
@@ -157,13 +327,26 @@ export function renderCanonicalStateMarkdown(state) {
157
327
  }
158
328
  lines.push("");
159
329
  }
160
- if (state.phaseHistory.length > 0) {
330
+ if (state.milestones.length > 0) {
161
331
  lines.push("## Phase History", "");
162
- for (const phase of state.phaseHistory) {
163
- const range = phase.closedAtRound
164
- ? `R${phase.enteredAtRound}-R${phase.closedAtRound}`
165
- : `R${phase.enteredAtRound}-current`;
166
- lines.push(`### ${phase.label}`, `Range: ${range} | Status: ${phase.status}`, "", `**Outcome**: ${phase.outcome}`, "");
332
+ for (const m of state.milestones) {
333
+ const kindIcon = m.kind === "agent_declared"
334
+ ? "🏁"
335
+ : m.kind === "criteria_milestone" ? "✅" : "📍";
336
+ lines.push(`### ${kindIcon} ${m.label}`, `Rounds ${m.round_range.start}–${m.round_range.end} | ` +
337
+ `Progress: ${(m.progress_at_boundary * 100).toFixed(0)}%`, "", `**Outcome**: ${m.outcome}`, "");
338
+ if (m.carried_constraints.length > 0) {
339
+ lines.push("**Carried Constraints**:");
340
+ for (const c of m.carried_constraints)
341
+ lines.push(`- ${c}`);
342
+ lines.push("");
343
+ }
344
+ if (m.resolved_constraints.length > 0) {
345
+ lines.push("**Resolved During Phase**:");
346
+ for (const c of m.resolved_constraints)
347
+ lines.push(`- ${c}`);
348
+ lines.push("");
349
+ }
167
350
  }
168
351
  }
169
352
  if (state.subGoals.length > 0) {
@@ -197,196 +380,89 @@ export function renderCanonicalStateMarkdown(state) {
197
380
  : "—";
198
381
  lines.push("## Agent Trust", "", `${bar} ${(state.agentTrustScore * 100).toFixed(0)}%`, `Trend (last ${state.agentTrustTrend.length}): ${state.agentTrustTrend.join(" → ")}`, `Average: ${avg}`, "");
199
382
  }
200
- if (state.loopSynthesis) {
201
- lines.push("## Loop Summary", "", state.loopSynthesis, "");
202
- }
203
383
  if (state.externalContext) {
204
384
  lines.push("## External Context", "", state.externalContext, "");
205
385
  }
206
386
  return lines.join("\n").trimEnd() + "\n";
207
387
  }
208
- export function createCanonicalLoopState(request, response, stateFilePath) {
209
- const last = request.last_round_view;
388
+ export function createCanonicalLoopState(request, response, stateFilePath,
389
+ /** v3.3: Display-only derived data (round stats, machine git-motion).
390
+ * Optional 4th param — callers that predate v3.3 stay on 3-arg calls. */
391
+ derived) {
392
+ const last = request.last_round_result;
210
393
  const objective = response.loop_objective;
211
394
  const verificationFlags = request.verification_flags ?? [];
212
395
  const rolling = response.rolling_summary;
213
- const evidence = last?.evidence;
396
+ const executionEvidence = last?.execution_evidence;
214
397
  const changes = unique([
215
398
  request.new_since_last_round,
216
- last?.summary,
217
- ...(last?.discoveries?.wrongAssumptions ?? []).map((value) => `Corrected assumption: ${value}`),
399
+ last?.output_summary,
400
+ ...(last?.wrong_assumptions ?? []).map((value) => `Corrected assumption: ${value}`),
218
401
  ]);
219
402
  const discoveries = unique([
220
- ...(last?.discoveries?.newConstraints ?? []),
221
- ...(last?.discoveries?.emergedWork ?? []),
222
- ...(response.projection?.discoveredFacts ?? []),
403
+ ...(last?.discovered_constraints ?? []),
404
+ ...(last?.emerged_subtasks ?? []),
223
405
  ]);
224
406
  const blockers = unique([
225
- ...(last?.violations ?? []),
407
+ ...(last?.constraint_violations ?? []),
408
+ last?.manual_fixes_needed,
226
409
  request.rejection_notice,
227
- ...(response.projection?.activeBlockers ?? []),
410
+ ...response.warnings,
228
411
  ]);
229
- const verifiedFacts = unique(response.projection?.verifiedFacts ?? []);
230
- const successCriteria = unique(objective?.success_criteria ?? []);
231
- const criterionMatches = (criterion) => {
232
- const normalizedCriterion = criterion.trim().replace(/\s+/g, " ").toLowerCase();
233
- return verifiedFacts.some((fact) => {
234
- const normalizedFact = fact.trim().replace(/\s+/g, " ").toLowerCase();
235
- return normalizedFact === normalizedCriterion || normalizedFact === `cr-${deriveItemId(criterion)}`;
236
- });
237
- };
238
- const remainingCriteria = successCriteria.filter((criterion) => !criterionMatches(criterion));
239
- const criteriaMet = successCriteria.filter(criterionMatches);
240
- const issueObligations = [...new Set(rolling?.recurring_issues ?? [])]
241
- .filter((issue) => /regress|回归|compatib|兼容|must remain|不能丢/i.test(issue))
242
- .map((description, index) => ({
243
- id: `reg-${deriveItemId(description)}`,
244
- description,
245
- sourceRound: Math.max(1, request.round - index),
246
- evidenceRefs: [],
247
- status: "open",
248
- }));
249
- const regressionObligations = response.projection?.regressionObligations?.length
250
- ? response.projection.regressionObligations
251
- : issueObligations;
252
- const phaseHistory = (rolling?.milestones ?? []).map((milestone, index) => ({
253
- id: `phase-${milestone.generated_at_round}-${index + 1}`,
254
- label: milestone.label,
255
- status: "completed",
256
- enteredAtRound: milestone.round_range.start,
257
- closedAtRound: milestone.round_range.end,
258
- outcome: milestone.outcome,
259
- evidenceRefs: [],
260
- }));
261
- const lastClosedPhase = phaseHistory.at(-1);
262
- if (lastClosedPhase) {
263
- phaseHistory.push({
264
- id: `phase-active-${lastClosedPhase.closedAtRound + 1}`,
265
- label: `After ${lastClosedPhase.label}`,
266
- status: "active",
267
- enteredAtRound: lastClosedPhase.closedAtRound + 1,
268
- outcome: "Work after the latest verified phase boundary is in progress.",
269
- evidenceRefs: [],
270
- });
271
- }
272
- else {
273
- phaseHistory.push({
274
- id: "phase-initial",
275
- label: "Execution",
276
- status: "active",
277
- enteredAtRound: 1,
278
- outcome: request.round <= 1 ? "Awaiting first accepted round." : "Work is in progress.",
279
- evidenceRefs: [],
280
- });
281
- }
282
- const verificationError = verificationFlags.find((flag) => flag.severity === "error");
283
- const activeSubGoal = (response.sub_goals ?? []).find((subGoal) => subGoal.status === "in_progress") ?? (response.sub_goals ?? []).find((subGoal) => subGoal.status === "pending");
284
- const currentFocus = verificationError?.detail
285
- || blockers[0]
286
- || activeSubGoal?.description
287
- || remainingCriteria[0]
288
- || objective?.objective
289
- || request.task;
290
- const suggestedNextAction = verificationError
291
- ? `Resolve verification finding: ${verificationError.detail}`
292
- : blockers[0]
293
- ? `Remove blocker with evidence: ${blockers[0]}`
294
- : activeSubGoal
295
- ? `Advance sub-goal ${activeSubGoal.id}: ${activeSubGoal.description}`
296
- : remainingCriteria[0]
297
- ? `Verify remaining criterion: ${remainingCriteria[0]}`
298
- : `Produce verifiable progress toward: ${currentFocus}`;
299
- const blockerGate = blockers[0] ? deriveGate(blockers[0], [blockers[0]]).gate : null;
300
- const activeGate = blockerGate?.kind === "user"
301
- ? blockerGate
302
- : verificationError
303
- ? {
304
- kind: "agent",
305
- problem: verificationError.detail,
306
- requiredEvidence: [verificationError.check],
307
- suggestedResolution: "Resolve the verification finding and rerun the affected check.",
308
- }
309
- : blockerGate;
310
- const todoProjection = currentFocus
311
- ? {
312
- id: deriveTodoId(currentFocus),
313
- sourceType: verificationError
314
- ? "verification"
315
- : blockers.length > 0
316
- ? "blocker"
317
- : activeSubGoal
318
- ? "sub_goal"
319
- : "criterion",
320
- sourceId: verificationError?.check ?? activeSubGoal?.id ?? "objective",
321
- action: suggestedNextAction || currentFocus,
322
- completionSignal: remainingCriteria[0]
323
- || "Provide verifiable evidence for the current focus.",
324
- evidenceRequirements: verificationError ? [verificationError.check] : [],
325
- gate: activeGate?.kind ?? "none",
326
- }
327
- : null;
328
- const handoff = {
329
- goalId: response.goal_id,
330
- roundId: `loop:${response.loop_id || request.loop_id}:round:${response.round || request.round}`,
331
- phaseId: phaseHistory.at(-1)?.id,
332
- focus: currentFocus,
333
- verifiedFacts: criteriaMet,
334
- unresolvedBlockers: blockers,
335
- carriedConstraints: unique(response.constraints_active)
336
- .slice(0, getPolicy().checkpoint.max_carried_constraints),
337
- failedAttempts: unique(rolling?.key_outcomes ?? [])
338
- .filter((outcome) => /\bfailed\b/i.test(outcome)),
339
- wrongAssumptions: unique(last?.discoveries?.wrongAssumptions ?? []),
340
- evidenceRefs: unique(response.projection?.verifiedEvidenceRefs ?? []),
341
- regressionObligations,
342
- suggestedNextAction: suggestedNextAction || undefined,
343
- gate: activeGate ?? undefined,
344
- };
412
+ // v3.4: The ACTIVE Round Contract (derived — loop-compiler computes it
413
+ // from committed rounds) drives the Current Task — the contract IS "what
414
+ // to do this round"; the original objective stays in the Objective
415
+ // section above. No active contract → the original task text is used,
416
+ // byte-identical to pre-v3.3 behavior.
417
+ const roundContract = derived?.roundContract ?? null;
345
418
  return {
346
- format: "loopforge.state",
419
+ schemaVersion: CANONICAL_STATE_SCHEMA_VERSION,
347
420
  loopId: response.loop_id || request.loop_id,
348
421
  round: response.round || request.round,
349
- maxRounds: request.max_rounds ?? getPolicy().engine.max_rounds,
422
+ maxRounds: request.max_rounds ?? 20,
350
423
  goalId: response.goal_id,
351
424
  objective: objective?.objective || request.task,
352
425
  objectiveVersion: objective?.version ?? 1,
353
- successCriteria,
426
+ currentTask: roundContract ? formatRoundContract(roundContract) : request.task,
427
+ successCriteria: unique(objective?.success_criteria ?? []),
354
428
  hardConstraints: unique(objective?.hard_constraints ?? []),
355
429
  activeConstraints: unique(response.constraints_active),
356
430
  retiredConstraints: unique(response.constraints_retired),
357
431
  inactiveConstraints: unique(response.constraints_inactive ?? []),
358
432
  constraintMetadata: response.constraint_metadata ?? [],
359
433
  changesSinceLastRound: changes,
360
- remainingCriteria,
434
+ remainingCriteria: unique(last?.execution_evidence?.success_criteria_remaining ?? []),
361
435
  blockers,
362
436
  verificationFlags,
363
437
  discoveries,
438
+ nextAction: last?.next_action?.trim() || response.suggested_next_task,
364
439
  rollingOutcomes: unique(rolling?.key_outcomes ?? []),
365
440
  recurringIssues: unique(rolling?.recurring_issues ?? []),
366
441
  failedPatterns: unique(rolling?.failed_patterns ?? []),
367
- loopSynthesis: rolling?.loop_synthesis ?? "",
442
+ milestones: rolling?.milestones ?? [],
368
443
  subGoals: response.sub_goals ?? [],
444
+ criterionStatuses: response.criterion_statuses ?? [],
445
+ lessons: response.lessons ?? [],
369
446
  agentTrustScore: response.agent_trust_score,
370
447
  agentTrustTrend: response.agent_trust_trend ?? [],
448
+ suggestedNextTask: response.suggested_next_task,
371
449
  externalContext: request.external_context?.trim() ?? "",
372
450
  stateFilePath,
373
451
  progress: {
374
- estimate: successCriteria.length > 0 ? criteriaMet.length / successCriteria.length : null,
375
- criteriaMet,
376
- criteriaRemaining: remainingCriteria,
377
- filesChanged: unique(evidence?.observedFiles ?? []),
378
- tests: evidence?.testCounts ?? null,
452
+ estimate: executionEvidence?.progress_estimate ?? null,
453
+ criteriaMet: unique(executionEvidence?.success_criteria_met ?? []),
454
+ criteriaRemaining: unique(executionEvidence?.success_criteria_remaining ?? []),
455
+ filesChanged: unique(executionEvidence?.files_changed ?? []),
456
+ tests: executionEvidence?.test_results ?? null,
379
457
  },
380
- regressionObligations,
381
- phaseHistory,
382
- currentFocus,
383
- todoProjection,
384
- suggestedNextAction,
385
- activeGate,
386
- stateFreshness: request.round <= 1 || evidence
387
- ? "fresh"
388
- : last ? "stale" : "missing",
389
- handoff,
458
+ // v3.3: Conditional presence — empty/absent derived data must not add
459
+ // keys, or every state hash would change for rounds without it.
460
+ ...(derived?.roundStats && derived.roundStats.length > 0
461
+ ? { roundStats: derived.roundStats }
462
+ : {}),
463
+ ...(derived?.machineStatus ? { machineStatus: derived.machineStatus } : {}),
464
+ // v3.3: Conditional presence — no contract, no key (hash stability).
465
+ ...(roundContract ? { roundContract } : {}),
390
466
  };
391
467
  }
392
468
  //# sourceMappingURL=canonical-state.js.map