loopforge 3.7.0 → 3.7.1

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 (95) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +17 -5
  3. package/dist/canonical-state.d.ts +23 -2
  4. package/dist/canonical-state.d.ts.map +1 -1
  5. package/dist/canonical-state.js +148 -27
  6. package/dist/canonical-state.js.map +1 -1
  7. package/dist/cli.d.ts.map +1 -1
  8. package/dist/cli.js +8 -8
  9. package/dist/cli.js.map +1 -1
  10. package/dist/cognitive-facts.d.ts.map +1 -1
  11. package/dist/cognitive-facts.js +2 -3
  12. package/dist/cognitive-facts.js.map +1 -1
  13. package/dist/cognitive-governance.d.ts +30 -6
  14. package/dist/cognitive-governance.d.ts.map +1 -1
  15. package/dist/cognitive-governance.js +74 -18
  16. package/dist/cognitive-governance.js.map +1 -1
  17. package/dist/committed-round.d.ts +7 -4
  18. package/dist/committed-round.d.ts.map +1 -1
  19. package/dist/committed-round.js +48 -8
  20. package/dist/committed-round.js.map +1 -1
  21. package/dist/enforcement-gate.d.ts +24 -19
  22. package/dist/enforcement-gate.d.ts.map +1 -1
  23. package/dist/enforcement-gate.js +100 -20
  24. package/dist/enforcement-gate.js.map +1 -1
  25. package/dist/engine.d.ts +10 -5
  26. package/dist/engine.d.ts.map +1 -1
  27. package/dist/engine.js +50 -30
  28. package/dist/engine.js.map +1 -1
  29. package/dist/evidence-provider.d.ts +7 -2
  30. package/dist/evidence-provider.d.ts.map +1 -1
  31. package/dist/evidence-provider.js +54 -9
  32. package/dist/evidence-provider.js.map +1 -1
  33. package/dist/generate-schema.js +104 -43
  34. package/dist/generate-schema.js.map +1 -1
  35. package/dist/index.d.ts +2 -1
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +2 -1
  38. package/dist/index.js.map +1 -1
  39. package/dist/loop-compiler.d.ts +18 -4
  40. package/dist/loop-compiler.d.ts.map +1 -1
  41. package/dist/loop-compiler.js +167 -83
  42. package/dist/loop-compiler.js.map +1 -1
  43. package/dist/mcp/round-lifecycle.d.ts +10 -0
  44. package/dist/mcp/round-lifecycle.d.ts.map +1 -1
  45. package/dist/mcp/round-lifecycle.js +126 -17
  46. package/dist/mcp/round-lifecycle.js.map +1 -1
  47. package/dist/mcp/server.d.ts.map +1 -1
  48. package/dist/mcp/server.js +12 -3
  49. package/dist/mcp/server.js.map +1 -1
  50. package/dist/mcp/session.d.ts +32 -2
  51. package/dist/mcp/session.d.ts.map +1 -1
  52. package/dist/mcp/session.js +166 -50
  53. package/dist/mcp/session.js.map +1 -1
  54. package/dist/mcp/tools.d.ts.map +1 -1
  55. package/dist/mcp/tools.js +169 -38
  56. package/dist/mcp/tools.js.map +1 -1
  57. package/dist/policy.d.ts +10 -2
  58. package/dist/policy.d.ts.map +1 -1
  59. package/dist/policy.js +3 -2
  60. package/dist/policy.js.map +1 -1
  61. package/dist/prompt-assembler.d.ts +6 -1
  62. package/dist/prompt-assembler.d.ts.map +1 -1
  63. package/dist/prompt-assembler.js +41 -48
  64. package/dist/prompt-assembler.js.map +1 -1
  65. package/dist/protocol.d.ts +47 -27
  66. package/dist/protocol.d.ts.map +1 -1
  67. package/dist/protocol.js +5 -6
  68. package/dist/protocol.js.map +1 -1
  69. package/dist/round-coordinator.d.ts +19 -0
  70. package/dist/round-coordinator.d.ts.map +1 -1
  71. package/dist/round-coordinator.js +62 -4
  72. package/dist/round-coordinator.js.map +1 -1
  73. package/dist/round-driver.d.ts +4 -0
  74. package/dist/round-driver.d.ts.map +1 -1
  75. package/dist/round-driver.js +2 -0
  76. package/dist/round-driver.js.map +1 -1
  77. package/dist/round-transaction.d.ts +4 -0
  78. package/dist/round-transaction.d.ts.map +1 -1
  79. package/dist/round-transaction.js +9 -0
  80. package/dist/round-transaction.js.map +1 -1
  81. package/dist/self-eval.d.ts +41 -3
  82. package/dist/self-eval.d.ts.map +1 -1
  83. package/dist/self-eval.js +125 -13
  84. package/dist/self-eval.js.map +1 -1
  85. package/dist/verification-gate.d.ts +14 -16
  86. package/dist/verification-gate.d.ts.map +1 -1
  87. package/dist/verification-gate.js +162 -93
  88. package/dist/verification-gate.js.map +1 -1
  89. package/dist/version.d.ts +5 -0
  90. package/dist/version.d.ts.map +1 -0
  91. package/dist/version.js +5 -0
  92. package/dist/version.js.map +1 -0
  93. package/loop_policy.json +19 -3
  94. package/package.json +7 -7
  95. package/skills/perception/SKILL.md +53 -15
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  /** LoopForge — Cognitive State Runtime for AI coding agents.
2
2
  *
3
- * TypeScript reference implementation v3.7.0
3
+ * TypeScript reference implementation. Version is single-sourced from
4
+ * package.json (see src/version.ts).
4
5
  *
5
6
  * Usage:
6
7
  * import { LoopForgeEngine, ReplayBackend, compileLoop } from "loopforge";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,EACL,IAAI,EACJ,WAAW,EACX,qBAAqB,EACrB,kBAAkB,EAClB,iBAAiB,EACjB,cAAc,EACd,kBAAkB,EAClB,iBAAiB,EACjB,mBAAmB,EACnB,sBAAsB,EACtB,uBAAuB,EACvB,gBAAgB,EAChB,UAAU,EACV,oBAAoB,EACpB,oBAAoB,EACpB,sBAAsB,EACtB,qBAAqB,EACrB,oBAAoB,EACpB,WAAW,EACX,kBAAkB,GACnB,MAAM,eAAe,CAAC;AAEvB,YAAY,EACV,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EAChB,aAAa,EACb,UAAU,EACV,cAAc,EACd,aAAa,EACb,eAAe,EACf,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACjB,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,OAAO,EACP,cAAc,EACd,cAAc,GACf,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,SAAS,EACT,UAAU,EACV,WAAW,EACX,cAAc,EACd,cAAc,GACf,MAAM,aAAa,CAAC;AAErB,YAAY,EACV,UAAU,EACV,iBAAiB,EACjB,aAAa,EACb,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,eAAe,EACf,cAAc,EACd,qBAAqB,EACrB,SAAS,GACV,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,yBAAyB,GAC1B,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACV,SAAS,EACT,mBAAmB,EACnB,iBAAiB,EACjB,UAAU,GACX,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EACL,WAAW,EACX,WAAW,EACX,SAAS,EACT,eAAe,EACf,mBAAmB,EACnB,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAG5C,OAAO,EACL,eAAe,EACf,YAAY,EACZ,mBAAmB,EACnB,sBAAsB,EACtB,uBAAuB,EACvB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAErB,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAElE,YAAY,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAGhD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,YAAY,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAEtE,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,uBAAuB,EACvB,wBAAwB,EACxB,0BAA0B,EAC1B,yBAAyB,EACzB,aAAa,EACb,uBAAuB,GACxB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EACV,gBAAgB,EAChB,gBAAgB,EAChB,sBAAsB,EACtB,qBAAqB,EACrB,sBAAsB,EACtB,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EACL,QAAQ,GACT,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,YAAY,GACb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,sBAAsB,EACtB,aAAa,EACb,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAGjE,OAAO,EACL,gCAAgC,EAChC,2BAA2B,EAC3B,WAAW,EACX,uBAAuB,EACvB,6BAA6B,GAC9B,MAAM,wBAAwB,CAAC;AAChC,YAAY,EACV,qBAAqB,EACrB,wBAAwB,EACxB,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  /** LoopForge — Cognitive State Runtime for AI coding agents.
2
2
  *
3
- * TypeScript reference implementation v3.7.0
3
+ * TypeScript reference implementation. Version is single-sourced from
4
+ * package.json (see src/version.ts).
4
5
  *
5
6
  * Usage:
6
7
  * import { LoopForgeEngine, ReplayBackend, compileLoop } from "loopforge";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,iBAAiB;AACjB,OAAO,EACL,IAAI,EACJ,WAAW,EACX,qBAAqB,EACrB,kBAAkB,EAClB,iBAAiB,EACjB,cAAc,EACd,kBAAkB,EAClB,iBAAiB,EACjB,mBAAmB,EACnB,sBAAsB,EACtB,uBAAuB,EACvB,gBAAgB,EAChB,UAAU,EACV,oBAAoB,EACpB,oBAAoB,EACpB,sBAAsB,EACtB,qBAAqB,EACrB,oBAAoB,EACpB,WAAW,EACX,kBAAkB,GACnB,MAAM,eAAe,CAAC;AA4BvB,SAAS;AACT,OAAO,EACL,SAAS,EACT,UAAU,EACV,WAAW,EACX,cAAc,EACd,cAAc,GACf,MAAM,aAAa,CAAC;AAerB,gBAAgB;AAChB,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,yBAAyB,GAC1B,MAAM,iBAAiB,CAAC;AAQzB,gBAAgB;AAChB,OAAO,EACL,WAAW,EACX,WAAW,EACX,SAAS,EACT,eAAe,EACf,mBAAmB,EACnB,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,oBAAoB,CAAC;AAE5B,SAAS;AACT,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,SAAS;AACT,OAAO,EACL,eAAe,EACf,YAAY,EACZ,mBAAmB,EACnB,sBAAsB,EACtB,uBAAuB,EACvB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAMrB,aAAa;AACb,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,2BAA2B;AAC3B,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,uBAAuB,EACvB,wBAAwB,EACxB,0BAA0B,EAC1B,yBAAyB,EACzB,aAAa,EACb,uBAAuB,GACxB,MAAM,wBAAwB,CAAC;AAWhC,8DAA8D;AAC9D,OAAO,EACL,QAAQ,GACT,MAAM,oBAAoB,CAAC;AAI5B,OAAO,EACL,sBAAsB,EACtB,aAAa,EACb,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,qBAAqB,CAAC;AAG7B,oCAAoC;AACpC,OAAO,EACL,gCAAgC,EAChC,2BAA2B,EAC3B,WAAW,EACX,uBAAuB,EACvB,6BAA6B,GAC9B,MAAM,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,iBAAiB;AACjB,OAAO,EACL,IAAI,EACJ,WAAW,EACX,qBAAqB,EACrB,kBAAkB,EAClB,iBAAiB,EACjB,cAAc,EACd,kBAAkB,EAClB,iBAAiB,EACjB,mBAAmB,EACnB,sBAAsB,EACtB,uBAAuB,EACvB,gBAAgB,EAChB,UAAU,EACV,oBAAoB,EACpB,oBAAoB,EACpB,sBAAsB,EACtB,qBAAqB,EACrB,oBAAoB,EACpB,WAAW,EACX,kBAAkB,GACnB,MAAM,eAAe,CAAC;AA4BvB,SAAS;AACT,OAAO,EACL,SAAS,EACT,UAAU,EACV,WAAW,EACX,cAAc,EACd,cAAc,GACf,MAAM,aAAa,CAAC;AAerB,gBAAgB;AAChB,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,yBAAyB,GAC1B,MAAM,iBAAiB,CAAC;AAQzB,gBAAgB;AAChB,OAAO,EACL,WAAW,EACX,WAAW,EACX,SAAS,EACT,eAAe,EACf,mBAAmB,EACnB,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,oBAAoB,CAAC;AAE5B,SAAS;AACT,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,SAAS;AACT,OAAO,EACL,eAAe,EACf,YAAY,EACZ,mBAAmB,EACnB,sBAAsB,EACtB,uBAAuB,EACvB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAMrB,aAAa;AACb,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,2BAA2B;AAC3B,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,uBAAuB,EACvB,wBAAwB,EACxB,0BAA0B,EAC1B,yBAAyB,EACzB,aAAa,EACb,uBAAuB,GACxB,MAAM,wBAAwB,CAAC;AAWhC,8DAA8D;AAC9D,OAAO,EACL,QAAQ,GACT,MAAM,oBAAoB,CAAC;AAI5B,OAAO,EACL,sBAAsB,EACtB,aAAa,EACb,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,qBAAqB,CAAC;AAG7B,oCAAoC;AACpC,OAAO,EACL,gCAAgC,EAChC,2BAA2B,EAC3B,WAAW,EACX,uBAAuB,EACvB,6BAA6B,GAC9B,MAAM,wBAAwB,CAAC"}
@@ -3,7 +3,7 @@
3
3
  * The compiler evolves structured state and renders one prompt artifact.
4
4
  * L0/L1/L2 control state density only; the external Agent owns reasoning.
5
5
  */
6
- import { type CriterionStatus, type Lesson, type LoopCompileRequest, type LoopCompileResponse, type LoopHealth, type LoopObjective, type LoopRoundResult, type RollingSummary, type SubGoal, type TaskAlignment, type VerificationFlag } from "./protocol.js";
6
+ import { type CriterionStatus, type Lesson, type LoopCompileRequest, type LoopCompileResponse, type LoopHealth, type LoopObjective, type LoopRoundResult, type RollingSummary, type SubGoal, type SubGoalUpdate, type TaskAlignment, type VerificationFlag } from "./protocol.js";
7
7
  import type { PresentedStateSnapshot } from "./canonical-state.js";
8
8
  export interface PreviousRound {
9
9
  round: number;
@@ -50,20 +50,34 @@ export declare function deriveConstraintId(text: string): string;
50
50
  /** v2.11: Derive a stable criterion ID from its text hash (cr-XXXXXXXX).
51
51
  * Same hash strategy as SubGoal — deterministic across rounds. */
52
52
  export declare function deriveCriterionId(text: string): string;
53
+ /** Closed migration matrix. done/canceled are terminal (no out-edges); the
54
+ * matrix rejects re-opening. blocked → in_progress is the recovery path. */
55
+ export declare const SUBGOAL_TRANSITIONS: Record<SubGoalUpdate["status"], readonly SubGoal["status"][]>;
56
+ /** Whether a transition is legal. Same-status is a legal no-op (used by
57
+ * replay idempotency). References to terminal sub-goals are rejected by
58
+ * validateSubGoalUpdates before this is consulted. */
59
+ export declare function canTransitionSubGoal(from: SubGoal["status"], to: SubGoalUpdate["status"]): boolean;
60
+ /** Referential validation of a payload's subgoal_updates against the
61
+ * derived sub-goal set. Returns one error per invalid entry:
62
+ * unknown_id | terminal_reference | illegal_transition. */
63
+ export declare function validateSubGoalUpdates(subGoals: SubGoal[], updates: SubGoalUpdate[]): Array<{
64
+ id: string;
65
+ reason: string;
66
+ }>;
53
67
  export declare function alignTask(proposedTask: string, request: LoopCompileRequest, context: Record<string, unknown> | null): TaskAlignment;
54
68
  export declare function checkLoopHealth(loopId: string, request: LoopCompileRequest, context: Record<string, unknown> | null): LoopHealth;
55
69
  export declare function decideLevel(request: LoopCompileRequest, context: Record<string, unknown> | null): "l0" | "l1" | "l2";
56
- export declare function buildSelfEvalBlock(round: number, prevDriftFlags?: VerificationFlag[],
70
+ export declare function buildSelfEvalBlock(round: number, prevDriftFlags?: VerificationFlag[],
57
71
  /** v2.12: L0 is the minimal retry template — the v2.12 declarative fields
58
72
  * (outcome/blocker/retroactiveClaims) are L1/L2 additions so the retry
59
73
  * prompt stays within its tight budget. */
60
- level?: "l0" | "l1" | "l2",
74
+ level?: "l0" | "l1" | "l2",
61
75
  /** v3.3/v3.4: Whether this round's Current Task IS the ACTIVE Round
62
76
  * Contract (derived from committed rounds — compileLoop passes
63
77
  * `activeContract != null`). Only then does the template ask the agent
64
78
  * to restate/propose it — a generic empty contract template would invite
65
79
  * placeholder submissions that trigger round_underspecified noise. */
66
- hasContract?: boolean,
80
+ hasContract?: boolean,
67
81
  /** v3.5: L2-only prose suggesting a Round Contract declaration when the
68
82
  * Current Task is NOT one (contract_nudge_on_l2 policy, computed at the
69
83
  * compileLoop call site). Mutually exclusive with hasContract. The prose
@@ -1 +1 @@
1
- {"version":3,"file":"loop-compiler.d.ts","sourceRoot":"","sources":["../src/loop-compiler.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"loop-compiler.d.ts","sourceRoot":"","sources":["../src/loop-compiler.ts"],"names":[],"mappings":"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"}
@@ -7,7 +7,7 @@ import { createHash } from "node:crypto";
7
7
  import { getPolicy } from "./policy.js";
8
8
  import { AgentStatus, makeLoopCompileResponse, makeLoopHealth, makeLoopObjective, makeConstraintMeta, makeMilestoneSummary, makeRollingSummary, makeSubGoal, makeTaskAlignment, } from "./protocol.js";
9
9
  import { createCanonicalLoopState, renderCanonicalStateMarkdown, } from "./canonical-state.js";
10
- import { decodeRound, entryLineage, entryCriteriaMet, entryCriteriaRemaining, entryActiveConstraints, entryRetractedConstraints, entryProgressEstimate, entryEmergedSubtasks, machineGitMotionSeries, mergedRoundsFromEntries, } from "./committed-round.js";
10
+ import { decodeRound, entryLineage, entryCriteriaMet, entryCriteriaRemaining, entryActiveConstraints, entryRetractedConstraints, entryProgressEstimate, entryEmergedSubtasks, entrySubGoalUpdates, machineGitMotionSeries, mergedRoundsFromEntries, } from "./committed-round.js";
11
11
  import { contractRoundEvaluations, deriveActiveRoundContract, } from "./round-contract.js";
12
12
  import { assemblePromptArtifact } from "./prompt-assembler.js";
13
13
  import { decidePromptLevel, } from "./prompt-policy.js";
@@ -144,9 +144,16 @@ function evolveObjective(request, context) {
144
144
  let version = current.version ?? 1;
145
145
  if (last?.objective_refinement?.trim()) {
146
146
  const refinement = last.objective_refinement.trim();
147
- history.push(refinement);
148
- objective = `${objective}\nRefinement: ${refinement}`;
149
- version++;
147
+ // M5: fold each refinement exactly once per persisted objective state.
148
+ // A retry recompiles the same round against the same committed
149
+ // last_round_result; without this guard every retry attempt re-appends
150
+ // the refinement and inflates the version — the objective text and the
151
+ // state file grew "Refinement: X" once per attempt.
152
+ if (!history.includes(refinement)) {
153
+ history.push(refinement);
154
+ objective = `${objective}\nRefinement: ${refinement}`;
155
+ version++;
156
+ }
150
157
  }
151
158
  const revisions = new Map((last?.revised_success_criteria ?? []).map((item) => [item.old, item.new]));
152
159
  return makeLoopObjective({
@@ -609,11 +616,10 @@ function isCriterionId(ref) {
609
616
  return /^cr-[a-f0-9]{8}$/.test(ref);
610
617
  }
611
618
  /**
612
- * Match a user-provided sub-task reference against existing SubGoals.
613
- *
614
- * Tries exact SubGoal ID match first (e.g. "sg-a3f2b1c0"), then falls
615
- * back to Jaccard token similarity on descriptions. Returns the index
616
- * of the best-matching SubGoal, or -1 when no match reaches threshold.
619
+ * Match a user-provided description against existing SubGoals for DEDUP
620
+ * (emerged_subtasks creation). Exact ID first, then Jaccard similarity on
621
+ * descriptions. v3.7.1: status transitions no longer use this — they are
622
+ * strict ID references validated before the round advances.
617
623
  */
618
624
  function matchSubGoal(target, subGoals, threshold) {
619
625
  const trimmed = target.trim();
@@ -634,34 +640,90 @@ function matchSubGoal(target, subGoals, threshold) {
634
640
  }
635
641
  return bestScore >= threshold ? bestIdx : -1;
636
642
  }
643
+ // ═══════════════════════════════════════════════════════════════════════════
644
+ // Sub-goal transition matrix (v3.7.1 — single source)
645
+ // ═══════════════════════════════════════════════════════════════════════════
646
+ /** Closed migration matrix. done/canceled are terminal (no out-edges); the
647
+ * matrix rejects re-opening. blocked → in_progress is the recovery path. */
648
+ export const SUBGOAL_TRANSITIONS = {
649
+ in_progress: ["pending", "blocked"],
650
+ done: ["pending", "in_progress", "blocked"],
651
+ blocked: ["pending", "in_progress"],
652
+ canceled: ["pending", "in_progress", "blocked"],
653
+ };
654
+ /** Whether a transition is legal. Same-status is a legal no-op (used by
655
+ * replay idempotency). References to terminal sub-goals are rejected by
656
+ * validateSubGoalUpdates before this is consulted. */
657
+ export function canTransitionSubGoal(from, to) {
658
+ if (from === to)
659
+ return true;
660
+ return SUBGOAL_TRANSITIONS[to].includes(from);
661
+ }
662
+ /** Referential validation of a payload's subgoal_updates against the
663
+ * derived sub-goal set. Returns one error per invalid entry:
664
+ * unknown_id | terminal_reference | illegal_transition. */
665
+ export function validateSubGoalUpdates(subGoals, updates) {
666
+ const byId = new Map(subGoals.map((sg) => [sg.id, sg]));
667
+ const errors = [];
668
+ for (const update of updates) {
669
+ const target = byId.get(update.id);
670
+ if (!target) {
671
+ errors.push({ id: update.id, reason: "unknown_id" });
672
+ continue;
673
+ }
674
+ if (target.status === "done" || target.status === "canceled") {
675
+ errors.push({ id: update.id, reason: "terminal_reference" });
676
+ continue;
677
+ }
678
+ if (!canTransitionSubGoal(target.status, update.status)) {
679
+ errors.push({ id: update.id, reason: "illegal_transition" });
680
+ }
681
+ }
682
+ return errors;
683
+ }
684
+ /** Apply one transition to the accumulated list. Guards make replay of
685
+ * committed history idempotent: terminal sub-goals never change and
686
+ * same-status updates are no-ops. */
687
+ function applySubGoalUpdate(subGoals, update, rnd) {
688
+ const target = subGoals.find((sg) => sg.id === update.id);
689
+ if (!target)
690
+ return;
691
+ if (target.status === "done" || target.status === "canceled")
692
+ return;
693
+ if (!canTransitionSubGoal(target.status, update.status))
694
+ return;
695
+ if (target.status === update.status)
696
+ return;
697
+ target.status = update.status;
698
+ target.status_changed_at_round = rnd;
699
+ if (update.status === "done")
700
+ target.completed_at_round = rnd;
701
+ }
637
702
  /**
638
703
  * Manage the full sub-goal lifecycle across rounds.
639
704
  *
640
- * Accumulates sub-goals from all prior vault entries, processes the current
641
- * round's declarations, derives status changes, and returns the complete
642
- * structured SubGoal list.
643
- *
644
- * Status derivation priority (compiler-driven):
645
- * 1. Agent declared done/canceled/blocked → explicit status change
646
- * 2. next_action match → auto in_progress
647
- * 3. success_criteria_met match → auto done
705
+ * v3.7.1 derivation rule: EVERY committed round (round < currentRound) is
706
+ * replayed in round order — emerged_subtasks create pending items, then
707
+ * subgoal_updates apply explicit transitions. The round's own report
708
+ * (last_round_result, which the engine rebuilds from the same committed
709
+ * evaluation) contributes the same data on the fixture path, so applying
710
+ * it again is an idempotent no-op. Older transitions therefore never
711
+ * regress on later compiles, and no status is ever inferred.
648
712
  */
649
713
  function manageSubGoals(loopId, currentRound, lastRoundResult, vaultContext) {
650
714
  const policy = getPolicy().evolution;
651
715
  const allEntries = loopEntries(loopId, vaultContext);
652
- const completedEntries = allEntries.filter((e) => entryRound(e) >= 1 && entryRound(e) < currentRound);
716
+ const completedEntries = allEntries
717
+ .filter((e) => entryRound(e) >= 1 && entryRound(e) < currentRound)
718
+ .sort((a, b) => entryRound(a) - entryRound(b));
653
719
  const subGoals = [];
654
- // Phase 1 — Accumulate historical sub-goals from vault entries
655
- for (const entry of completedEntries) {
656
- const rnd = entryRound(entry);
657
- const emerged = entryEmergedSubtasks(entry);
658
- for (const desc of emerged) {
659
- const id = deriveSubGoalId(desc);
720
+ const createEmerged = (descs, rnd) => {
721
+ for (const desc of descs) {
660
722
  // Dedup: skip if an existing sub-goal is too similar
661
723
  if (matchSubGoal(desc, subGoals, policy.subgoal_dedup_threshold) >= 0)
662
724
  continue;
663
725
  subGoals.push(makeSubGoal({
664
- id,
726
+ id: deriveSubGoalId(desc),
665
727
  description: desc.trim(),
666
728
  status: "pending",
667
729
  declared_at_round: rnd,
@@ -669,56 +731,24 @@ function manageSubGoals(loopId, currentRound, lastRoundResult, vaultContext) {
669
731
  priority: subGoals.length,
670
732
  }));
671
733
  }
734
+ };
735
+ // Phase 1 — Replay every committed round in order: create, then apply
736
+ // that round's declared transitions.
737
+ for (const entry of completedEntries) {
738
+ const rnd = entryRound(entry);
739
+ createEmerged(entryEmergedSubtasks(entry), rnd);
740
+ for (const update of entrySubGoalUpdates(entry)) {
741
+ applySubGoalUpdate(subGoals, update, rnd);
742
+ }
672
743
  }
673
- // Phase 2 — Process current round's sub-task declarations
744
+ // Phase 2 — The current round's own report (fixture/rebuild path; on the
745
+ // vault path this duplicates the last committed round and is idempotent).
674
746
  if (lastRoundResult) {
675
747
  const rnd = lastRoundResult.round || (currentRound - 1);
676
- // New sub-goals from this round
677
- const emerged = lastRoundResult.emerged_subtasks ?? [];
678
- for (const desc of emerged) {
679
- if (matchSubGoal(desc, subGoals, policy.subgoal_dedup_threshold) >= 0)
680
- continue;
681
- subGoals.push(makeSubGoal({
682
- id: deriveSubGoalId(desc),
683
- description: desc.trim(),
684
- status: "pending",
685
- declared_at_round: rnd,
686
- status_changed_at_round: rnd,
687
- priority: subGoals.length,
688
- }));
689
- }
690
- // Completed sub-goals
691
- const completed = lastRoundResult.completed_subtasks ?? [];
692
- for (const desc of completed) {
693
- const idx = matchSubGoal(desc, subGoals, policy.subgoal_match_threshold);
694
- if (idx >= 0 && subGoals[idx].status !== "done" && subGoals[idx].status !== "canceled") {
695
- subGoals[idx].status = "done";
696
- subGoals[idx].status_changed_at_round = rnd;
697
- subGoals[idx].completed_at_round = rnd;
698
- }
748
+ createEmerged(lastRoundResult.emerged_subtasks ?? [], rnd);
749
+ for (const update of lastRoundResult.subgoal_updates ?? []) {
750
+ applySubGoalUpdate(subGoals, update, rnd);
699
751
  }
700
- // Blocked sub-goals
701
- const blocked = lastRoundResult.blocked_subtasks ?? [];
702
- for (const desc of blocked) {
703
- const idx = matchSubGoal(desc, subGoals, policy.subgoal_match_threshold);
704
- if (idx >= 0 && subGoals[idx].status !== "done" && subGoals[idx].status !== "canceled") {
705
- subGoals[idx].status = "blocked";
706
- subGoals[idx].status_changed_at_round = rnd;
707
- }
708
- }
709
- // Canceled sub-goals
710
- const canceled = lastRoundResult.canceled_subtasks ?? [];
711
- for (const desc of canceled) {
712
- const idx = matchSubGoal(desc, subGoals, policy.subgoal_match_threshold);
713
- if (idx >= 0 && subGoals[idx].status !== "done" && subGoals[idx].status !== "canceled") {
714
- subGoals[idx].status = "canceled";
715
- subGoals[idx].status_changed_at_round = rnd;
716
- }
717
- }
718
- // v3.7: no auto status inference. The v2.3 fuzzy heuristics
719
- // (next_action Jaccard → in_progress, criteria Jaccard → done) were
720
- // removed — they fabricated dashboard motion the machine never
721
- // verified. Statuses change only via the agent declarations above.
722
752
  }
723
753
  // Sort for display: in_progress first, then pending by age desc, then blocked, then done, then canceled
724
754
  subGoals.sort((a, b) => {
@@ -910,17 +940,17 @@ function levelDecision(request, context) {
910
940
  export function decideLevel(request, context) {
911
941
  return levelDecision(request, context).level;
912
942
  }
913
- export function buildSelfEvalBlock(round, prevDriftFlags,
943
+ export function buildSelfEvalBlock(round, prevDriftFlags,
914
944
  /** v2.12: L0 is the minimal retry template — the v2.12 declarative fields
915
945
  * (outcome/blocker/retroactiveClaims) are L1/L2 additions so the retry
916
946
  * prompt stays within its tight budget. */
917
- level,
947
+ level,
918
948
  /** v3.3/v3.4: Whether this round's Current Task IS the ACTIVE Round
919
949
  * Contract (derived from committed rounds — compileLoop passes
920
950
  * `activeContract != null`). Only then does the template ask the agent
921
951
  * to restate/propose it — a generic empty contract template would invite
922
952
  * placeholder submissions that trigger round_underspecified noise. */
923
- hasContract = false,
953
+ hasContract = false,
924
954
  /** v3.5: L2-only prose suggesting a Round Contract declaration when the
925
955
  * Current Task is NOT one (contract_nudge_on_l2 policy, computed at the
926
956
  * compileLoop call site). Mutually exclusive with hasContract. The prose
@@ -948,9 +978,9 @@ proposalNudge = false) {
948
978
  },
949
979
  wrong_assumptions: [],
950
980
  next_action: "<next concrete action, or empty when complete>",
951
- completed_subtasks: ["<sg-XXXXXXXX or text>"],
952
- blocked_subtasks: ["<sg-XXXXXXXX or text>"],
953
- canceled_subtasks: ["<sg-XXXXXXXX or text>"],
981
+ subgoal_updates: [
982
+ { id: "<sg-XXXXXXXX from the Sub-Goal Dashboard>", status: "done" },
983
+ ],
954
984
  };
955
985
  // v2.12: Declarative tri-state outcome (L1/L2 only — keeps L0 retry lean)
956
986
  if (declareOutcome) {
@@ -999,7 +1029,8 @@ proposalNudge = false) {
999
1029
  lines.push("IMPORTANT: Replace every <placeholder> with your actual data.");
1000
1030
  lines.push("Set success=true only when the full goal and ALL hard constraints are verified.");
1001
1031
  lines.push("Use IDs for exact matching: c-XXXXXXXX, cr-XXXXXXXX, sg-XXXXXXXX.");
1002
- lines.push("For subtasks, use sub-goal IDs from the Sub-Goal Dashboard above.");
1032
+ lines.push("Sub-goal changes go through `subgoal_updates` (active sub-goal IDs only;");
1033
+ lines.push("done/canceled are terminal — reopen via `emerged_subtasks`; bad IDs reject).");
1003
1034
  // v3.5: L2 contract-less nudge (prose only — never the JSON key name, so
1004
1035
  // contract-less rounds keep their template-lean assertions).
1005
1036
  if (proposalNudge) {
@@ -1013,6 +1044,43 @@ proposalNudge = false) {
1013
1044
  // v2.5: checkpoint() removed — agent_declared milestones from
1014
1045
  // compression_checkpoint supersede the separate CheckpointSummary.
1015
1046
  // See buildRollingSummary() signal 1.
1047
+ /** v3.7.1: Derived Recovery Brief for the state-file Recent tier. Present
1048
+ * ONLY while a committed backtrack decision is the current round's record
1049
+ * (the recovery window); the redo commit replaces that record, so the
1050
+ * brief exits the projection by construction. Sources are the committed
1051
+ * rollback decision's own fields — no new persistence, no rejected
1052
+ * payloads. */
1053
+ function committedBacktrackBrief(loopId, round, context) {
1054
+ const entry = loopEntries(loopId, context).find((e) => entryLineage(e).committed_action === "backtrack" && entryRound(e) === round);
1055
+ if (!entry)
1056
+ return undefined;
1057
+ // Read the facts the engine stamped onto the merged lineage entry from
1058
+ // the committed rollback decision (engine.ts mergeCommittedRound).
1059
+ const lineage = entryLineage(entry);
1060
+ const asStrings = (value) => Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
1061
+ const asNumbers = (value) => Array.isArray(value) ? value.filter((v) => typeof v === "number") : [];
1062
+ const failedRounds = asNumbers(lineage.backtrackFailedRounds);
1063
+ const approaches = asStrings(lineage.backtrackApproaches).slice(0, 6);
1064
+ const assumptions = asStrings(lineage.backtrackWrongAssumptions).slice(0, 5);
1065
+ const target = typeof lineage.backtrackTarget === "number" ? lineage.backtrackTarget : 0;
1066
+ const trigger = typeof lineage.backtrackTriggerRule === "string"
1067
+ ? lineage.backtrackTriggerRule
1068
+ : "unknown";
1069
+ const lines = [
1070
+ `- **Trigger**: ${trigger}`,
1071
+ `- **Restored to round**: ${target} (redo round ${target + 1})`,
1072
+ ];
1073
+ if (failedRounds.length > 0)
1074
+ lines.push(`- **Failed rounds**: ${failedRounds.join(", ")}`);
1075
+ for (const approach of approaches)
1076
+ lines.push(` - Failed approach: ${approach}`);
1077
+ if (assumptions.length > 0) {
1078
+ lines.push("- **Falsified assumptions** (do not rebuild on these):");
1079
+ for (const assumption of assumptions)
1080
+ lines.push(` - ${assumption}`);
1081
+ }
1082
+ return lines;
1083
+ }
1016
1084
  export function compileLoop(request, context) {
1017
1085
  const decision = levelDecision(request, context);
1018
1086
  const previous = getPreviousRound(request.loop_id, request.round - 1, context);
@@ -1085,14 +1153,27 @@ export function compileLoop(request, context) {
1085
1153
  warnings,
1086
1154
  });
1087
1155
  const policy = getPolicy();
1088
- const statePath = `${policy.state_file.directory}/${request.loop_id}-state.md`;
1156
+ // L7 (v3.7.x): the state-file path is empty when the file is disabled —
1157
+ // prompts must never point at (or collapse content behind) a file that
1158
+ // writeStateFile will not create. An empty path propagates into the
1159
+ // assembler's pointer lines and the L1 collapse gate below.
1160
+ const statePath = policy.state_file.enabled
1161
+ ? `${policy.state_file.directory}/${request.loop_id}-state.md`
1162
+ : "";
1089
1163
  const state = createCanonicalLoopState(request, response, statePath, {
1090
1164
  roundStats,
1091
1165
  machineStatus,
1092
1166
  // v3.4: derived active contract — the Current Task's source of truth.
1093
1167
  roundContract: activeContract,
1094
1168
  });
1095
- const markdown = renderCanonicalStateMarkdown(state);
1169
+ const markdown = renderCanonicalStateMarkdown(state, {
1170
+ // v3.7.1: retry attempts are marked in the derived view (attempt is
1171
+ // request context, not a committed fact). The Recovery Brief renders in
1172
+ // the Recent tier only while a committed backtrack decision is this
1173
+ // round's record; the redo commit removes it by construction.
1174
+ attempt: request.attempt,
1175
+ recoveryBrief: committedBacktrackBrief(request.loop_id, request.round, context),
1176
+ });
1096
1177
  // v2.4–v2.5: Adaptive L2 budget — scales with loop complexity
1097
1178
  const adaptiveL2 = policy.prompt.l2_adaptive_enabled
1098
1179
  ? Math.min(policy.prompt.l2_max_chars
@@ -1103,7 +1184,10 @@ export function compileLoop(request, context) {
1103
1184
  // v2.8: L2 pointer mode — when enabled, skip the monolithic markdown blob.
1104
1185
  // Structured L2 sections (milestones, sub-goals, trust, progress) still
1105
1186
  // render. The state file on disk is the durable source of truth.
1106
- const l2Pointer = policy.prompt.l2_pointer_enabled && decision.level === "l2";
1187
+ // L7: pointer mode REQUIRES the file — with state_file disabled it would
1188
+ // silently drop the full state from L2 prompts and point at nothing.
1189
+ const l2Pointer = policy.state_file.enabled &&
1190
+ policy.prompt.l2_pointer_enabled && decision.level === "l2";
1107
1191
  const artifact = assemblePromptArtifact({
1108
1192
  state,
1109
1193
  level: decision.level,
@@ -1114,11 +1198,11 @@ export function compileLoop(request, context) {
1114
1198
  l2: adaptiveL2,
1115
1199
  },
1116
1200
  attempt: request.attempt,
1117
- selfEvaluationBlock: buildSelfEvalBlock(request.round, request.verification_flags, decision.level,
1201
+ selfEvaluationBlock: buildSelfEvalBlock(request.round, request.verification_flags, decision.level,
1118
1202
  // v3.4: restate the contract only when this prompt's Current Task IS
1119
1203
  // the ACTIVE contract (derived — not the previous submission's field,
1120
1204
  // which is a proposal and may differ from what this round executes).
1121
- activeContract != null,
1205
+ activeContract != null,
1122
1206
  // v3.5: L2-only declaration nudge when nothing is active (policy-gated;
1123
1207
  // mutually exclusive with the restate template above).
1124
1208
  decision.level === "l2" && activeContract === null &&