@soleri/core 2.4.0 → 2.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 (328) hide show
  1. package/dist/brain/brain.d.ts +7 -0
  2. package/dist/brain/brain.d.ts.map +1 -1
  3. package/dist/brain/brain.js +56 -9
  4. package/dist/brain/brain.js.map +1 -1
  5. package/dist/brain/intelligence.d.ts +1 -0
  6. package/dist/brain/intelligence.d.ts.map +1 -1
  7. package/dist/brain/intelligence.js +164 -148
  8. package/dist/brain/intelligence.js.map +1 -1
  9. package/dist/brain/types.d.ts +2 -2
  10. package/dist/brain/types.d.ts.map +1 -1
  11. package/dist/cognee/client.d.ts +3 -0
  12. package/dist/cognee/client.d.ts.map +1 -1
  13. package/dist/cognee/client.js +17 -0
  14. package/dist/cognee/client.js.map +1 -1
  15. package/dist/cognee/sync-manager.d.ts +94 -0
  16. package/dist/cognee/sync-manager.d.ts.map +1 -0
  17. package/dist/cognee/sync-manager.js +293 -0
  18. package/dist/cognee/sync-manager.js.map +1 -0
  19. package/dist/control/identity-manager.d.ts +3 -1
  20. package/dist/control/identity-manager.d.ts.map +1 -1
  21. package/dist/control/identity-manager.js +49 -51
  22. package/dist/control/identity-manager.js.map +1 -1
  23. package/dist/control/intent-router.d.ts +1 -0
  24. package/dist/control/intent-router.d.ts.map +1 -1
  25. package/dist/control/intent-router.js +32 -32
  26. package/dist/control/intent-router.js.map +1 -1
  27. package/dist/curator/curator.d.ts +9 -1
  28. package/dist/curator/curator.d.ts.map +1 -1
  29. package/dist/curator/curator.js +104 -92
  30. package/dist/curator/curator.js.map +1 -1
  31. package/dist/errors/classify.d.ts +13 -0
  32. package/dist/errors/classify.d.ts.map +1 -0
  33. package/dist/errors/classify.js +97 -0
  34. package/dist/errors/classify.js.map +1 -0
  35. package/dist/errors/index.d.ts +6 -0
  36. package/dist/errors/index.d.ts.map +1 -0
  37. package/dist/errors/index.js +4 -0
  38. package/dist/errors/index.js.map +1 -0
  39. package/dist/errors/retry.d.ts +40 -0
  40. package/dist/errors/retry.d.ts.map +1 -0
  41. package/dist/errors/retry.js +97 -0
  42. package/dist/errors/retry.js.map +1 -0
  43. package/dist/errors/types.d.ts +48 -0
  44. package/dist/errors/types.d.ts.map +1 -0
  45. package/dist/errors/types.js +59 -0
  46. package/dist/errors/types.js.map +1 -0
  47. package/dist/governance/governance.d.ts +1 -0
  48. package/dist/governance/governance.d.ts.map +1 -1
  49. package/dist/governance/governance.js +51 -68
  50. package/dist/governance/governance.js.map +1 -1
  51. package/dist/index.d.ts +26 -5
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +22 -3
  54. package/dist/index.js.map +1 -1
  55. package/dist/intake/content-classifier.d.ts +14 -0
  56. package/dist/intake/content-classifier.d.ts.map +1 -0
  57. package/dist/intake/content-classifier.js +125 -0
  58. package/dist/intake/content-classifier.js.map +1 -0
  59. package/dist/intake/dedup-gate.d.ts +17 -0
  60. package/dist/intake/dedup-gate.d.ts.map +1 -0
  61. package/dist/intake/dedup-gate.js +66 -0
  62. package/dist/intake/dedup-gate.js.map +1 -0
  63. package/dist/intake/intake-pipeline.d.ts +63 -0
  64. package/dist/intake/intake-pipeline.d.ts.map +1 -0
  65. package/dist/intake/intake-pipeline.js +373 -0
  66. package/dist/intake/intake-pipeline.js.map +1 -0
  67. package/dist/intake/types.d.ts +65 -0
  68. package/dist/intake/types.d.ts.map +1 -0
  69. package/dist/intake/types.js +3 -0
  70. package/dist/intake/types.js.map +1 -0
  71. package/dist/intelligence/loader.js +1 -1
  72. package/dist/intelligence/loader.js.map +1 -1
  73. package/dist/intelligence/types.d.ts +3 -1
  74. package/dist/intelligence/types.d.ts.map +1 -1
  75. package/dist/loop/loop-manager.d.ts +58 -7
  76. package/dist/loop/loop-manager.d.ts.map +1 -1
  77. package/dist/loop/loop-manager.js +280 -6
  78. package/dist/loop/loop-manager.js.map +1 -1
  79. package/dist/loop/types.d.ts +69 -1
  80. package/dist/loop/types.d.ts.map +1 -1
  81. package/dist/loop/types.js +4 -1
  82. package/dist/loop/types.js.map +1 -1
  83. package/dist/persistence/index.d.ts +4 -0
  84. package/dist/persistence/index.d.ts.map +1 -0
  85. package/dist/persistence/index.js +3 -0
  86. package/dist/persistence/index.js.map +1 -0
  87. package/dist/persistence/postgres-provider.d.ts +46 -0
  88. package/dist/persistence/postgres-provider.d.ts.map +1 -0
  89. package/dist/persistence/postgres-provider.js +115 -0
  90. package/dist/persistence/postgres-provider.js.map +1 -0
  91. package/dist/persistence/sqlite-provider.d.ts +28 -0
  92. package/dist/persistence/sqlite-provider.d.ts.map +1 -0
  93. package/dist/persistence/sqlite-provider.js +97 -0
  94. package/dist/persistence/sqlite-provider.js.map +1 -0
  95. package/dist/persistence/types.d.ts +58 -0
  96. package/dist/persistence/types.d.ts.map +1 -0
  97. package/dist/persistence/types.js +8 -0
  98. package/dist/persistence/types.js.map +1 -0
  99. package/dist/planning/gap-analysis.d.ts +47 -4
  100. package/dist/planning/gap-analysis.d.ts.map +1 -1
  101. package/dist/planning/gap-analysis.js +190 -13
  102. package/dist/planning/gap-analysis.js.map +1 -1
  103. package/dist/planning/gap-types.d.ts +1 -1
  104. package/dist/planning/gap-types.d.ts.map +1 -1
  105. package/dist/planning/gap-types.js.map +1 -1
  106. package/dist/planning/planner.d.ts +277 -9
  107. package/dist/planning/planner.d.ts.map +1 -1
  108. package/dist/planning/planner.js +611 -46
  109. package/dist/planning/planner.js.map +1 -1
  110. package/dist/playbooks/generic/brainstorming.d.ts +9 -0
  111. package/dist/playbooks/generic/brainstorming.d.ts.map +1 -0
  112. package/dist/playbooks/generic/brainstorming.js +105 -0
  113. package/dist/playbooks/generic/brainstorming.js.map +1 -0
  114. package/dist/playbooks/generic/code-review.d.ts +11 -0
  115. package/dist/playbooks/generic/code-review.d.ts.map +1 -0
  116. package/dist/playbooks/generic/code-review.js +176 -0
  117. package/dist/playbooks/generic/code-review.js.map +1 -0
  118. package/dist/playbooks/generic/subagent-execution.d.ts +9 -0
  119. package/dist/playbooks/generic/subagent-execution.d.ts.map +1 -0
  120. package/dist/playbooks/generic/subagent-execution.js +68 -0
  121. package/dist/playbooks/generic/subagent-execution.js.map +1 -0
  122. package/dist/playbooks/generic/systematic-debugging.d.ts +9 -0
  123. package/dist/playbooks/generic/systematic-debugging.d.ts.map +1 -0
  124. package/dist/playbooks/generic/systematic-debugging.js +87 -0
  125. package/dist/playbooks/generic/systematic-debugging.js.map +1 -0
  126. package/dist/playbooks/generic/tdd.d.ts +9 -0
  127. package/dist/playbooks/generic/tdd.d.ts.map +1 -0
  128. package/dist/playbooks/generic/tdd.js +70 -0
  129. package/dist/playbooks/generic/tdd.js.map +1 -0
  130. package/dist/playbooks/generic/verification.d.ts +9 -0
  131. package/dist/playbooks/generic/verification.d.ts.map +1 -0
  132. package/dist/playbooks/generic/verification.js +74 -0
  133. package/dist/playbooks/generic/verification.js.map +1 -0
  134. package/dist/playbooks/index.d.ts +4 -0
  135. package/dist/playbooks/index.d.ts.map +1 -0
  136. package/dist/playbooks/index.js +5 -0
  137. package/dist/playbooks/index.js.map +1 -0
  138. package/dist/playbooks/playbook-registry.d.ts +42 -0
  139. package/dist/playbooks/playbook-registry.d.ts.map +1 -0
  140. package/dist/playbooks/playbook-registry.js +227 -0
  141. package/dist/playbooks/playbook-registry.js.map +1 -0
  142. package/dist/playbooks/playbook-seeder.d.ts +47 -0
  143. package/dist/playbooks/playbook-seeder.d.ts.map +1 -0
  144. package/dist/playbooks/playbook-seeder.js +104 -0
  145. package/dist/playbooks/playbook-seeder.js.map +1 -0
  146. package/dist/playbooks/playbook-types.d.ts +132 -0
  147. package/dist/playbooks/playbook-types.d.ts.map +1 -0
  148. package/dist/playbooks/playbook-types.js +12 -0
  149. package/dist/playbooks/playbook-types.js.map +1 -0
  150. package/dist/project/project-registry.d.ts +4 -4
  151. package/dist/project/project-registry.d.ts.map +1 -1
  152. package/dist/project/project-registry.js +30 -57
  153. package/dist/project/project-registry.js.map +1 -1
  154. package/dist/prompts/index.d.ts +4 -0
  155. package/dist/prompts/index.d.ts.map +1 -0
  156. package/dist/prompts/index.js +3 -0
  157. package/dist/prompts/index.js.map +1 -0
  158. package/dist/prompts/parser.d.ts +17 -0
  159. package/dist/prompts/parser.d.ts.map +1 -0
  160. package/dist/prompts/parser.js +47 -0
  161. package/dist/prompts/parser.js.map +1 -0
  162. package/dist/prompts/template-manager.d.ts +25 -0
  163. package/dist/prompts/template-manager.d.ts.map +1 -0
  164. package/dist/prompts/template-manager.js +71 -0
  165. package/dist/prompts/template-manager.js.map +1 -0
  166. package/dist/prompts/types.d.ts +26 -0
  167. package/dist/prompts/types.d.ts.map +1 -0
  168. package/dist/prompts/types.js +5 -0
  169. package/dist/prompts/types.js.map +1 -0
  170. package/dist/runtime/admin-extra-ops.d.ts +5 -3
  171. package/dist/runtime/admin-extra-ops.d.ts.map +1 -1
  172. package/dist/runtime/admin-extra-ops.js +348 -11
  173. package/dist/runtime/admin-extra-ops.js.map +1 -1
  174. package/dist/runtime/admin-ops.d.ts.map +1 -1
  175. package/dist/runtime/admin-ops.js +10 -3
  176. package/dist/runtime/admin-ops.js.map +1 -1
  177. package/dist/runtime/capture-ops.d.ts.map +1 -1
  178. package/dist/runtime/capture-ops.js +20 -2
  179. package/dist/runtime/capture-ops.js.map +1 -1
  180. package/dist/runtime/cognee-sync-ops.d.ts +12 -0
  181. package/dist/runtime/cognee-sync-ops.d.ts.map +1 -0
  182. package/dist/runtime/cognee-sync-ops.js +55 -0
  183. package/dist/runtime/cognee-sync-ops.js.map +1 -0
  184. package/dist/runtime/core-ops.d.ts +8 -6
  185. package/dist/runtime/core-ops.d.ts.map +1 -1
  186. package/dist/runtime/core-ops.js +226 -9
  187. package/dist/runtime/core-ops.js.map +1 -1
  188. package/dist/runtime/curator-extra-ops.d.ts +2 -2
  189. package/dist/runtime/curator-extra-ops.d.ts.map +1 -1
  190. package/dist/runtime/curator-extra-ops.js +15 -3
  191. package/dist/runtime/curator-extra-ops.js.map +1 -1
  192. package/dist/runtime/domain-ops.js +2 -2
  193. package/dist/runtime/domain-ops.js.map +1 -1
  194. package/dist/runtime/grading-ops.d.ts.map +1 -1
  195. package/dist/runtime/grading-ops.js.map +1 -1
  196. package/dist/runtime/intake-ops.d.ts +14 -0
  197. package/dist/runtime/intake-ops.d.ts.map +1 -0
  198. package/dist/runtime/intake-ops.js +110 -0
  199. package/dist/runtime/intake-ops.js.map +1 -0
  200. package/dist/runtime/loop-ops.d.ts +5 -4
  201. package/dist/runtime/loop-ops.d.ts.map +1 -1
  202. package/dist/runtime/loop-ops.js +84 -12
  203. package/dist/runtime/loop-ops.js.map +1 -1
  204. package/dist/runtime/memory-cross-project-ops.d.ts.map +1 -1
  205. package/dist/runtime/memory-cross-project-ops.js.map +1 -1
  206. package/dist/runtime/memory-extra-ops.js +5 -5
  207. package/dist/runtime/memory-extra-ops.js.map +1 -1
  208. package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
  209. package/dist/runtime/orchestrate-ops.js +8 -2
  210. package/dist/runtime/orchestrate-ops.js.map +1 -1
  211. package/dist/runtime/planning-extra-ops.d.ts +13 -5
  212. package/dist/runtime/planning-extra-ops.d.ts.map +1 -1
  213. package/dist/runtime/planning-extra-ops.js +381 -18
  214. package/dist/runtime/planning-extra-ops.js.map +1 -1
  215. package/dist/runtime/playbook-ops.d.ts +14 -0
  216. package/dist/runtime/playbook-ops.d.ts.map +1 -0
  217. package/dist/runtime/playbook-ops.js +141 -0
  218. package/dist/runtime/playbook-ops.js.map +1 -0
  219. package/dist/runtime/project-ops.d.ts.map +1 -1
  220. package/dist/runtime/project-ops.js +7 -2
  221. package/dist/runtime/project-ops.js.map +1 -1
  222. package/dist/runtime/runtime.d.ts.map +1 -1
  223. package/dist/runtime/runtime.js +28 -9
  224. package/dist/runtime/runtime.js.map +1 -1
  225. package/dist/runtime/types.d.ts +8 -0
  226. package/dist/runtime/types.d.ts.map +1 -1
  227. package/dist/runtime/vault-extra-ops.d.ts +4 -2
  228. package/dist/runtime/vault-extra-ops.d.ts.map +1 -1
  229. package/dist/runtime/vault-extra-ops.js +383 -4
  230. package/dist/runtime/vault-extra-ops.js.map +1 -1
  231. package/dist/vault/playbook.d.ts +34 -0
  232. package/dist/vault/playbook.d.ts.map +1 -0
  233. package/dist/vault/playbook.js +60 -0
  234. package/dist/vault/playbook.js.map +1 -0
  235. package/dist/vault/vault.d.ts +52 -32
  236. package/dist/vault/vault.d.ts.map +1 -1
  237. package/dist/vault/vault.js +300 -181
  238. package/dist/vault/vault.js.map +1 -1
  239. package/package.json +9 -3
  240. package/src/__tests__/admin-extra-ops.test.ts +62 -15
  241. package/src/__tests__/admin-ops.test.ts +2 -2
  242. package/src/__tests__/brain.test.ts +3 -3
  243. package/src/__tests__/cognee-integration.test.ts +80 -0
  244. package/src/__tests__/cognee-sync-manager.test.ts +103 -0
  245. package/src/__tests__/core-ops.test.ts +36 -4
  246. package/src/__tests__/curator-extra-ops.test.ts +24 -2
  247. package/src/__tests__/errors.test.ts +388 -0
  248. package/src/__tests__/grading-ops.test.ts +28 -7
  249. package/src/__tests__/intake-pipeline.test.ts +162 -0
  250. package/src/__tests__/loop-ops.test.ts +74 -3
  251. package/src/__tests__/memory-cross-project-ops.test.ts +3 -1
  252. package/src/__tests__/orchestrate-ops.test.ts +8 -3
  253. package/src/__tests__/persistence.test.ts +291 -0
  254. package/src/__tests__/planner.test.ts +99 -21
  255. package/src/__tests__/planning-extra-ops.test.ts +168 -10
  256. package/src/__tests__/playbook-registry.test.ts +326 -0
  257. package/src/__tests__/playbook-seeder.test.ts +163 -0
  258. package/src/__tests__/playbook.test.ts +389 -0
  259. package/src/__tests__/postgres-provider.test.ts +58 -0
  260. package/src/__tests__/project-ops.test.ts +18 -4
  261. package/src/__tests__/template-manager.test.ts +222 -0
  262. package/src/__tests__/vault-extra-ops.test.ts +82 -7
  263. package/src/__tests__/vault.test.ts +184 -0
  264. package/src/brain/brain.ts +71 -9
  265. package/src/brain/intelligence.ts +258 -307
  266. package/src/brain/types.ts +2 -2
  267. package/src/cognee/client.ts +18 -0
  268. package/src/cognee/sync-manager.ts +389 -0
  269. package/src/control/identity-manager.ts +77 -75
  270. package/src/control/intent-router.ts +55 -57
  271. package/src/curator/curator.ts +199 -139
  272. package/src/errors/classify.ts +102 -0
  273. package/src/errors/index.ts +5 -0
  274. package/src/errors/retry.ts +132 -0
  275. package/src/errors/types.ts +81 -0
  276. package/src/governance/governance.ts +90 -107
  277. package/src/index.ts +116 -3
  278. package/src/intake/content-classifier.ts +146 -0
  279. package/src/intake/dedup-gate.ts +92 -0
  280. package/src/intake/intake-pipeline.ts +503 -0
  281. package/src/intake/types.ts +69 -0
  282. package/src/intelligence/loader.ts +1 -1
  283. package/src/intelligence/types.ts +3 -1
  284. package/src/loop/loop-manager.ts +325 -7
  285. package/src/loop/types.ts +72 -1
  286. package/src/persistence/index.ts +9 -0
  287. package/src/persistence/postgres-provider.ts +157 -0
  288. package/src/persistence/sqlite-provider.ts +115 -0
  289. package/src/persistence/types.ts +74 -0
  290. package/src/planning/gap-analysis.ts +286 -17
  291. package/src/planning/gap-types.ts +4 -1
  292. package/src/planning/planner.ts +828 -55
  293. package/src/playbooks/generic/brainstorming.ts +110 -0
  294. package/src/playbooks/generic/code-review.ts +181 -0
  295. package/src/playbooks/generic/subagent-execution.ts +74 -0
  296. package/src/playbooks/generic/systematic-debugging.ts +92 -0
  297. package/src/playbooks/generic/tdd.ts +75 -0
  298. package/src/playbooks/generic/verification.ts +79 -0
  299. package/src/playbooks/index.ts +27 -0
  300. package/src/playbooks/playbook-registry.ts +284 -0
  301. package/src/playbooks/playbook-seeder.ts +119 -0
  302. package/src/playbooks/playbook-types.ts +162 -0
  303. package/src/project/project-registry.ts +81 -74
  304. package/src/prompts/index.ts +3 -0
  305. package/src/prompts/parser.ts +59 -0
  306. package/src/prompts/template-manager.ts +77 -0
  307. package/src/prompts/types.ts +28 -0
  308. package/src/runtime/admin-extra-ops.ts +391 -13
  309. package/src/runtime/admin-ops.ts +17 -6
  310. package/src/runtime/capture-ops.ts +25 -6
  311. package/src/runtime/cognee-sync-ops.ts +63 -0
  312. package/src/runtime/core-ops.ts +258 -8
  313. package/src/runtime/curator-extra-ops.ts +17 -3
  314. package/src/runtime/domain-ops.ts +2 -2
  315. package/src/runtime/grading-ops.ts +11 -2
  316. package/src/runtime/intake-ops.ts +126 -0
  317. package/src/runtime/loop-ops.ts +96 -13
  318. package/src/runtime/memory-cross-project-ops.ts +1 -2
  319. package/src/runtime/memory-extra-ops.ts +5 -5
  320. package/src/runtime/orchestrate-ops.ts +8 -2
  321. package/src/runtime/planning-extra-ops.ts +414 -23
  322. package/src/runtime/playbook-ops.ts +169 -0
  323. package/src/runtime/project-ops.ts +9 -3
  324. package/src/runtime/runtime.ts +36 -10
  325. package/src/runtime/types.ts +8 -0
  326. package/src/runtime/vault-extra-ops.ts +425 -4
  327. package/src/vault/playbook.ts +87 -0
  328. package/src/vault/vault.ts +419 -235
@@ -1,16 +1,150 @@
1
1
  /**
2
- * Loop manager — lightweight in-memory state tracker for iterative
3
- * validation loops.
2
+ * Loop manager — iterative validation loop state tracking with
3
+ * output scanning and gate decision system.
4
4
  *
5
- * Tracks active loop, iteration history, and past loop results.
6
- * Session-scoped (no persistence across restarts).
5
+ * Ported from Salvador's loop.facade.ts with:
6
+ * - Promise tag extraction (<promise>...</promise>)
7
+ * - 5-tier heuristic completion detection
8
+ * - Gate decision pattern (allow/block) for Stop hook integration
9
+ * - Knowledge tracking for brain session recording
10
+ *
11
+ * Session-scoped (in-memory). Persistence is optional via external store.
12
+ */
13
+
14
+ import type {
15
+ LoopConfig,
16
+ LoopIteration,
17
+ LoopMode,
18
+ LoopState,
19
+ LoopKnowledge,
20
+ LoopHistoryEntry,
21
+ LoopIterateDecision,
22
+ } from './types.js';
23
+
24
+ // ─── Grade ordering for plan-iteration mode ──────────────────────
25
+ const GRADE_ORDER = ['A+', 'A', 'B', 'C', 'D', 'F'];
26
+
27
+ // ─── Output Scanning ─────────────────────────────────────────────
28
+
29
+ /**
30
+ * Extract text between <promise>...</promise> tags from output.
31
+ * Ported from Salvador's extractPromise.
32
+ */
33
+ export function extractPromise(text: string): string | null {
34
+ const match = /<promise>([\s\S]*?)<\/promise>/.exec(text);
35
+ if (!match) return null;
36
+ return match[1].trim().replace(/\s+/g, ' ');
37
+ }
38
+
39
+ /**
40
+ * 5-tier heuristic completion detection.
41
+ * Returns a reason string if completion is detected, null otherwise.
42
+ * Ported from Salvador's detectImplicitCompletion.
43
+ *
44
+ * Tiers:
45
+ * 1. Validation tool score (token-migration, component-build)
46
+ * 2. Contrast mode — all PASS, no FAIL
47
+ * 3. Plan mode — grade meets target
48
+ * 4. Completion language + file modification signals (requires BOTH)
49
+ * 5. Test pass signals
50
+ */
51
+ export function detectImplicitCompletion(lastOutput: string, config: LoopConfig): string | null {
52
+ const text = lastOutput;
53
+
54
+ // 1. Validation tool score (token-migration, component-build)
55
+ if (config.mode === 'token-migration' || config.mode === 'component-build') {
56
+ const scoreMatch = /"score"\s*:\s*(\d+(?:\.\d+)?)/.exec(text);
57
+ if (scoreMatch) {
58
+ const score = parseFloat(scoreMatch[1]);
59
+ const target = config.targetScore ?? (config.mode === 'token-migration' ? 95 : 90);
60
+ if (score >= target) {
61
+ return `Auto-detected: validation score ${score} >= target ${target}`;
62
+ }
63
+ }
64
+ }
65
+
66
+ // 2. Contrast mode — all PASS, no FAIL
67
+ if (config.mode === 'contrast-fix') {
68
+ const hasPass = /\bPASS\b/i.test(text);
69
+ const hasFail = /\bFAIL\b/i.test(text);
70
+ if (hasPass && !hasFail) {
71
+ return 'Auto-detected: all contrast checks PASS, no FAIL';
72
+ }
73
+ }
74
+
75
+ // 3. Plan mode — grade meets target
76
+ if (config.mode === 'plan-iteration') {
77
+ const gradeMatch = /"grade"\s*:\s*"([A-F][+-]?)"/.exec(text);
78
+ if (gradeMatch) {
79
+ const grade = gradeMatch[1];
80
+ const target = config.targetGrade ?? 'A';
81
+ const gradeIdx = GRADE_ORDER.indexOf(grade);
82
+ const targetIdx = GRADE_ORDER.indexOf(target);
83
+ if (gradeIdx >= 0 && targetIdx >= 0 && gradeIdx <= targetIdx) {
84
+ return `Auto-detected: plan grade ${grade} >= target ${target}`;
85
+ }
86
+ }
87
+ }
88
+
89
+ // 4. Completion language + file modification signals (requires BOTH)
90
+ const completionPhrases =
91
+ /\b(task complete|implementation finished|implementation complete|work complete|all done|changes complete|finished implementing|done implementing)\b/i;
92
+ const fileModPhrases =
93
+ /\b(file modified|wrote to|saved to|created file|updated file|edited file|changes written)\b/i;
94
+ if (completionPhrases.test(text) && fileModPhrases.test(text)) {
95
+ return 'Auto-detected: completion language with file modification evidence';
96
+ }
97
+
98
+ // 5. Test pass signals
99
+ const testPass =
100
+ /(\d+)\s+tests?\s+passed[,\s]+0\s+fail/i.test(text) ||
101
+ /\ball\s+tests?\s+(passing|passed)\b/i.test(text);
102
+ if (testPass) {
103
+ return 'Auto-detected: test suite passing';
104
+ }
105
+
106
+ return null;
107
+ }
108
+
109
+ // ─── Anomaly Detection ──────────────────────────────────────────
110
+
111
+ /**
112
+ * Minimum expected duration (ms) per mode.
113
+ * Iterations finishing faster than this with low scores are flagged as anomalous.
7
114
  */
115
+ const MIN_DURATION_MS: Record<LoopMode, number> = {
116
+ 'token-migration': 5000,
117
+ 'contrast-fix': 2000,
118
+ 'component-build': 5000,
119
+ 'plan-iteration': 3000,
120
+ custom: 0,
121
+ };
122
+
123
+ /**
124
+ * Detect anomalous loop iteration patterns.
125
+ * Flags fast + low-score combos that suggest the agent is spinning without making progress.
126
+ */
127
+ export function detectAnomaly(iteration: LoopIteration, mode: LoopMode): string | null {
128
+ const minDuration = MIN_DURATION_MS[mode];
129
+ if (minDuration === 0) return null; // custom mode — no threshold
130
+
131
+ const duration = iteration.durationMs ?? 0;
132
+ const score = iteration.validationScore ?? 0;
8
133
 
9
- import type { LoopConfig, LoopIteration, LoopState } from './types.js';
134
+ // Flag: iteration completed very fast with a low score
135
+ if (duration > 0 && duration < minDuration && !iteration.passed && score < 50) {
136
+ return `Anomaly: iteration ${iteration.iteration} completed in ${duration}ms (min expected: ${minDuration}ms) with score ${score} — possible no-op loop`;
137
+ }
138
+
139
+ return null;
140
+ }
141
+
142
+ // ─── LoopManager ─────────────────────────────────────────────────
10
143
 
11
144
  export class LoopManager {
12
145
  private activeLoop: LoopState | null = null;
13
146
  private completedLoops: LoopState[] = [];
147
+ private historyEntries: LoopHistoryEntry[] = [];
14
148
 
15
149
  /**
16
150
  * Start a new validation loop.
@@ -37,9 +171,9 @@ export class LoopManager {
37
171
  }
38
172
 
39
173
  /**
40
- * Record an iteration result on the active loop.
174
+ * Record an iteration result on the active loop (simple API).
41
175
  * If the iteration passes, the loop status is NOT automatically changed —
42
- * call `completeLoop()` explicitly when validation is confirmed.
176
+ * call completeLoop() explicitly when validation is confirmed.
43
177
  * If max iterations reached and this iteration fails, status becomes 'max-iterations'.
44
178
  */
45
179
  iterate(result: {
@@ -68,6 +202,7 @@ export class LoopManager {
68
202
  ) {
69
203
  this.activeLoop.status = 'max-iterations';
70
204
  this.activeLoop.completedAt = new Date().toISOString();
205
+ this.moveToHistory('max_iterations');
71
206
  this.completedLoops.push(this.activeLoop);
72
207
  this.activeLoop = null;
73
208
  }
@@ -75,6 +210,163 @@ export class LoopManager {
75
210
  return iteration;
76
211
  }
77
212
 
213
+ /**
214
+ * Gate-based iteration — accepts LLM output and returns allow/block decision.
215
+ * Ported from Salvador's loop_iterate handler.
216
+ *
217
+ * This is the primary method for Stop hook integration:
218
+ * - Scans output for completion promise tags
219
+ * - Runs 5-tier heuristic completion detection
220
+ * - Returns 'block' with injected prompt/systemMessage for next iteration
221
+ * - Returns 'allow' when loop ends (completed, max_iterations)
222
+ *
223
+ * @param lastOutput - The LLM's last response to scan for completion signals
224
+ * @param knowledge - Optional knowledge items discovered during this iteration
225
+ */
226
+ iterateWithGate(
227
+ lastOutput: string,
228
+ knowledge?: LoopKnowledge,
229
+ durationMs?: number,
230
+ ): LoopIterateDecision {
231
+ if (!this.activeLoop) {
232
+ return { decision: 'allow', reason: 'No active loop' };
233
+ }
234
+
235
+ const config = this.activeLoop.config;
236
+
237
+ // Accumulate knowledge
238
+ if (knowledge) {
239
+ if (!this.activeLoop.knowledge) {
240
+ this.activeLoop.knowledge = {};
241
+ }
242
+ if (knowledge.items) {
243
+ this.activeLoop.knowledge.items = [
244
+ ...(this.activeLoop.knowledge.items ?? []),
245
+ ...knowledge.items,
246
+ ];
247
+ }
248
+ if (knowledge.patternsApplied) {
249
+ this.activeLoop.knowledge.patternsApplied = [
250
+ ...(this.activeLoop.knowledge.patternsApplied ?? []),
251
+ ...knowledge.patternsApplied,
252
+ ];
253
+ }
254
+ if (knowledge.antiPatternsAvoided) {
255
+ this.activeLoop.knowledge.antiPatternsAvoided = [
256
+ ...(this.activeLoop.knowledge.antiPatternsAvoided ?? []),
257
+ ...knowledge.antiPatternsAvoided,
258
+ ];
259
+ }
260
+ }
261
+
262
+ // Check for completion promise
263
+ if (config.completionPromise) {
264
+ const promiseText = extractPromise(lastOutput);
265
+ if (promiseText && promiseText === config.completionPromise) {
266
+ const iterCount = this.activeLoop.iterations.length;
267
+ this.activeLoop.status = 'completed';
268
+ this.activeLoop.completedAt = new Date().toISOString();
269
+ this.moveToHistory('completed');
270
+ this.completedLoops.push(this.activeLoop);
271
+ this.activeLoop = null;
272
+ return {
273
+ decision: 'allow',
274
+ reason: `Loop completed — promise "${config.completionPromise}" detected`,
275
+ outcome: 'completed',
276
+ iteration: iterCount,
277
+ };
278
+ }
279
+ }
280
+
281
+ // Heuristic completion detection
282
+ const autoReason = detectImplicitCompletion(lastOutput, config);
283
+ if (autoReason) {
284
+ const iterCount = this.activeLoop.iterations.length;
285
+ this.activeLoop.status = 'completed';
286
+ this.activeLoop.completedAt = new Date().toISOString();
287
+ this.moveToHistory('completed');
288
+ this.completedLoops.push(this.activeLoop);
289
+ this.activeLoop = null;
290
+ return {
291
+ decision: 'allow',
292
+ reason: autoReason,
293
+ outcome: 'completed',
294
+ iteration: iterCount,
295
+ autoCompleted: true,
296
+ };
297
+ }
298
+
299
+ // Check max iterations
300
+ if (config.maxIterations > 0 && this.activeLoop.iterations.length >= config.maxIterations) {
301
+ const iterCount = this.activeLoop.iterations.length;
302
+ this.activeLoop.status = 'max-iterations';
303
+ this.activeLoop.completedAt = new Date().toISOString();
304
+ this.moveToHistory('max_iterations');
305
+ this.completedLoops.push(this.activeLoop);
306
+ this.activeLoop = null;
307
+ return {
308
+ decision: 'allow',
309
+ reason: `Max iterations (${config.maxIterations}) reached`,
310
+ outcome: 'max_iterations',
311
+ iteration: iterCount,
312
+ };
313
+ }
314
+
315
+ // Continue loop — increment iteration
316
+ const nextIteration = this.activeLoop.iterations.length + 1;
317
+ const iterationEntry: LoopIteration = {
318
+ iteration: nextIteration,
319
+ timestamp: new Date().toISOString(),
320
+ passed: false,
321
+ ...(durationMs !== undefined && { durationMs }),
322
+ };
323
+ this.activeLoop.iterations.push(iterationEntry);
324
+
325
+ // Run anomaly detection
326
+ const anomalyWarning = detectAnomaly(iterationEntry, config.mode);
327
+
328
+ // Build validation hint for system message
329
+ let validationHint = '';
330
+ switch (config.mode) {
331
+ case 'token-migration':
332
+ validationHint = `Run validation (tokens check). Target: score >= ${config.targetScore ?? 95}`;
333
+ break;
334
+ case 'contrast-fix':
335
+ validationHint = 'Run contrast check on all color pairs. Target: all PASS';
336
+ break;
337
+ case 'component-build':
338
+ validationHint = `Run validation (full check). Target: score >= ${config.targetScore ?? 90}`;
339
+ break;
340
+ case 'plan-iteration':
341
+ validationHint = `Run plan grading. Target: grade >= ${config.targetGrade ?? 'A'}`;
342
+ break;
343
+ case 'custom':
344
+ validationHint = 'Complete the task and validate your work';
345
+ break;
346
+ }
347
+
348
+ const maxDisplay = config.maxIterations > 0 ? String(config.maxIterations) : 'unlimited';
349
+ const systemMessage =
350
+ `[Loop — Iteration ${nextIteration}/${maxDisplay} | Mode: ${config.mode}] ${validationHint}` +
351
+ (config.completionPromise
352
+ ? ` | Output <promise>${config.completionPromise}</promise> ONLY when validation passes`
353
+ : '');
354
+
355
+ // Build full prompt with validation instructions
356
+ const fullPrompt = config.validationInstructions
357
+ ? `${config.prompt}\n\n${config.validationInstructions}`
358
+ : config.prompt;
359
+
360
+ return {
361
+ decision: 'block',
362
+ reason: fullPrompt,
363
+ prompt: fullPrompt,
364
+ systemMessage,
365
+ iteration: nextIteration,
366
+ ...(anomalyWarning && { anomalyWarning }),
367
+ };
368
+ }
369
+
78
370
  /**
79
371
  * Mark the active loop as completed (validation passed).
80
372
  */
@@ -86,6 +378,7 @@ export class LoopManager {
86
378
  this.activeLoop.status = 'completed';
87
379
  this.activeLoop.completedAt = new Date().toISOString();
88
380
  const completed = this.activeLoop;
381
+ this.moveToHistory('completed');
89
382
  this.completedLoops.push(completed);
90
383
  this.activeLoop = null;
91
384
  return completed;
@@ -102,6 +395,7 @@ export class LoopManager {
102
395
  this.activeLoop.status = 'cancelled';
103
396
  this.activeLoop.completedAt = new Date().toISOString();
104
397
  const cancelled = this.activeLoop;
398
+ this.moveToHistory('cancelled');
105
399
  this.completedLoops.push(cancelled);
106
400
  this.activeLoop = null;
107
401
  return cancelled;
@@ -121,10 +415,34 @@ export class LoopManager {
121
415
  return [...this.completedLoops];
122
416
  }
123
417
 
418
+ /**
419
+ * Get structured history entries (for brain session recording).
420
+ */
421
+ getHistoryEntries(): LoopHistoryEntry[] {
422
+ return [...this.historyEntries];
423
+ }
424
+
124
425
  /**
125
426
  * Check if a loop is currently active.
126
427
  */
127
428
  isActive(): boolean {
128
429
  return this.activeLoop !== null;
129
430
  }
431
+
432
+ /**
433
+ * Move active loop to history entries.
434
+ */
435
+ private moveToHistory(outcome: 'completed' | 'cancelled' | 'max_iterations'): void {
436
+ if (!this.activeLoop) return;
437
+ this.historyEntries.push({
438
+ id: this.activeLoop.id,
439
+ mode: this.activeLoop.config.mode,
440
+ intent: this.activeLoop.config.intent,
441
+ prompt: this.activeLoop.config.prompt,
442
+ iterations: this.activeLoop.iterations.length,
443
+ outcome,
444
+ startedAt: this.activeLoop.startedAt,
445
+ completedAt: this.activeLoop.completedAt ?? new Date().toISOString(),
446
+ });
447
+ }
130
448
  }
package/src/loop/types.ts CHANGED
@@ -2,7 +2,10 @@
2
2
  * Loop system types — iterative validation loop state tracking.
3
3
  *
4
4
  * Loops let agents run validate-fix-validate cycles (e.g. token migration,
5
- * contrast fixes, component builds). In-memory only — session-scoped.
5
+ * contrast fixes, component builds).
6
+ *
7
+ * Ported from Salvador's loop.facade.ts with full gate decision system,
8
+ * output scanning, and completion promise detection.
6
9
  */
7
10
 
8
11
  /** Supported loop modes — each maps to a different validation strategy. */
@@ -18,7 +21,16 @@ export interface LoopConfig {
18
21
  mode: LoopMode;
19
22
  prompt: string;
20
23
  maxIterations: number;
24
+ /** Target validation score for score-based modes (token-migration, component-build). */
21
25
  targetScore?: number;
26
+ /** Target grade for plan-iteration mode (e.g., 'A', 'A+'). */
27
+ targetGrade?: string;
28
+ /** Completion promise text — loop completes when this appears in output. */
29
+ completionPromise?: string;
30
+ /** Validation instructions appended to the prompt each iteration. */
31
+ validationInstructions?: string;
32
+ /** Detected intent for this loop (e.g., 'BUILD', 'FIX'). Used for brain session recording. */
33
+ intent?: string;
22
34
  }
23
35
 
24
36
  /** A single iteration result within a loop. */
@@ -28,11 +40,26 @@ export interface LoopIteration {
28
40
  validationScore?: number;
29
41
  validationResult?: string;
30
42
  passed: boolean;
43
+ /** Duration of this iteration in milliseconds. */
44
+ durationMs?: number;
31
45
  }
32
46
 
33
47
  /** Loop lifecycle status. */
34
48
  export type LoopStatus = 'active' | 'completed' | 'cancelled' | 'max-iterations';
35
49
 
50
+ /**
51
+ * Knowledge items tracked during loop execution.
52
+ * Ported from Salvador's loop knowledge tracking for brain session recording.
53
+ */
54
+ export interface LoopKnowledge {
55
+ /** Knowledge items discovered during the loop. */
56
+ items?: string[];
57
+ /** Patterns successfully applied during the loop. */
58
+ patternsApplied?: string[];
59
+ /** Anti-patterns intentionally avoided during the loop. */
60
+ antiPatternsAvoided?: string[];
61
+ }
62
+
36
63
  /** Full state of a loop (active or historical). */
37
64
  export interface LoopState {
38
65
  id: string;
@@ -41,4 +68,48 @@ export interface LoopState {
41
68
  status: LoopStatus;
42
69
  startedAt: string;
43
70
  completedAt?: string;
71
+ /** Knowledge accumulated during the loop. */
72
+ knowledge?: LoopKnowledge;
73
+ }
74
+
75
+ /**
76
+ * Loop history entry — stored after loop completion.
77
+ * Ported from Salvador's LoopHistoryEntry.
78
+ */
79
+ export interface LoopHistoryEntry {
80
+ id: string;
81
+ mode: LoopMode;
82
+ intent?: string;
83
+ prompt: string;
84
+ iterations: number;
85
+ outcome: 'completed' | 'cancelled' | 'max_iterations';
86
+ startedAt: string;
87
+ completedAt: string;
88
+ }
89
+
90
+ /**
91
+ * Gate decision returned by iterateWithGate().
92
+ * Ported from Salvador's LoopIterateDecision.
93
+ *
94
+ * - 'allow': Loop has ended (completed, max_iterations, or error). Exit the loop.
95
+ * - 'block': Loop continues. The returned prompt and systemMessage are injected
96
+ * into the next iteration of the Stop hook.
97
+ */
98
+ export interface LoopIterateDecision {
99
+ /** Gate decision: 'allow' to exit loop, 'block' to continue iterating. */
100
+ decision: 'allow' | 'block';
101
+ /** Human-readable reason for this decision. */
102
+ reason: string;
103
+ /** Full prompt for the next iteration (only when decision = 'block'). */
104
+ prompt?: string;
105
+ /** System message for the next iteration (only when decision = 'block'). */
106
+ systemMessage?: string;
107
+ /** Current iteration number. */
108
+ iteration?: number;
109
+ /** Outcome when loop ends (only when decision = 'allow'). */
110
+ outcome?: 'completed' | 'max_iterations';
111
+ /** Whether completion was auto-detected (heuristic, not explicit promise). */
112
+ autoCompleted?: boolean;
113
+ /** Warning if anomalous patterns detected in this iteration. */
114
+ anomalyWarning?: string;
44
115
  }
@@ -0,0 +1,9 @@
1
+ export type {
2
+ PersistenceProvider,
3
+ PersistenceParams,
4
+ RunResult,
5
+ PersistenceConfig,
6
+ FtsSearchOptions,
7
+ } from './types.js';
8
+ export { SQLitePersistenceProvider } from './sqlite-provider.js';
9
+ export { PostgresPersistenceProvider, translateSql } from './postgres-provider.js';
@@ -0,0 +1,157 @@
1
+ /**
2
+ * PostgreSQL persistence provider (working stub).
3
+ *
4
+ * Implements PersistenceProvider with pg.Pool. The translateSql() function
5
+ * converts SQLite-style queries to PostgreSQL-compatible syntax.
6
+ *
7
+ * NOTE: PersistenceProvider is synchronous (better-sqlite3 heritage).
8
+ * This provider wraps async pg calls synchronously for interface compliance.
9
+ * Full async provider support is planned for v7.0.
10
+ */
11
+
12
+ import type {
13
+ PersistenceProvider,
14
+ PersistenceParams,
15
+ RunResult,
16
+ FtsSearchOptions,
17
+ } from './types.js';
18
+
19
+ /**
20
+ * Translate SQLite-style SQL to PostgreSQL-compatible SQL.
21
+ *
22
+ * - Converts positional `?` params to `$1, $2, ...`
23
+ * - Converts named `@name` params to `$N` positional, returns ordered values
24
+ * - Replaces `unixepoch()` with `EXTRACT(EPOCH FROM NOW())::integer`
25
+ */
26
+ export function translateSql(
27
+ sql: string,
28
+ params?: PersistenceParams,
29
+ ): { sql: string; values: unknown[] } {
30
+ let translated = sql.replace(/unixepoch\(\)/gi, 'EXTRACT(EPOCH FROM NOW())::integer');
31
+
32
+ if (!params) return { sql: translated, values: [] };
33
+
34
+ if (Array.isArray(params)) {
35
+ let idx = 0;
36
+ translated = translated.replace(/\?/g, () => `$${++idx}`);
37
+ return { sql: translated, values: params };
38
+ }
39
+
40
+ // Named params: @name -> $N
41
+ const values: unknown[] = [];
42
+ const nameMap = new Map<string, number>();
43
+ translated = translated.replace(/@(\w+)/g, (_match, name: string) => {
44
+ if (!nameMap.has(name)) {
45
+ nameMap.set(name, values.length + 1);
46
+ values.push(params[name]);
47
+ }
48
+ return `$${nameMap.get(name)}`;
49
+ });
50
+ return { sql: translated, values };
51
+ }
52
+
53
+ /**
54
+ * PostgreSQL persistence provider.
55
+ *
56
+ * Uses pg.Pool for connection management. Created via async factory
57
+ * `PostgresPersistenceProvider.create()`.
58
+ */
59
+ export class PostgresPersistenceProvider implements PersistenceProvider {
60
+ readonly backend = 'postgres' as const;
61
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
62
+ private pool: any;
63
+
64
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
65
+ private constructor(pool: any) {
66
+ this.pool = pool;
67
+ }
68
+
69
+ /**
70
+ * Async factory. Dynamically imports `pg` (optional dependency).
71
+ */
72
+ static async create(
73
+ connectionString: string,
74
+ poolSize = 10,
75
+ ): Promise<PostgresPersistenceProvider> {
76
+ const { default: pg } = await import('pg');
77
+ const pool = new pg.Pool({
78
+ connectionString,
79
+ max: poolSize,
80
+ idleTimeoutMillis: 30_000,
81
+ });
82
+ return new PostgresPersistenceProvider(pool);
83
+ }
84
+
85
+ execSql(sql: string): void {
86
+ // Sync wrapper -- logs warning in non-test env
87
+ // Full async support in v7.0
88
+ void sql;
89
+ throw new Error(
90
+ 'PostgresPersistenceProvider.execSql() is not yet implemented. ' +
91
+ 'Use SQLitePersistenceProvider for synchronous operations. ' +
92
+ 'Full PostgreSQL support requires async PersistenceProvider (v7.0).',
93
+ );
94
+ }
95
+
96
+ run(sql: string, params?: PersistenceParams): RunResult {
97
+ const _translated = translateSql(sql, params);
98
+ throw new Error(
99
+ 'PostgresPersistenceProvider.run() is not yet implemented. ' +
100
+ 'Full PostgreSQL support requires async PersistenceProvider (v7.0).',
101
+ );
102
+ }
103
+
104
+ get<T = Record<string, unknown>>(sql: string, params?: PersistenceParams): T | undefined {
105
+ const _translated = translateSql(sql, params);
106
+ throw new Error(
107
+ 'PostgresPersistenceProvider.get() is not yet implemented. ' +
108
+ 'Full PostgreSQL support requires async PersistenceProvider (v7.0).',
109
+ );
110
+ }
111
+
112
+ all<T = Record<string, unknown>>(sql: string, params?: PersistenceParams): T[] {
113
+ const _translated = translateSql(sql, params);
114
+ throw new Error(
115
+ 'PostgresPersistenceProvider.all() is not yet implemented. ' +
116
+ 'Full PostgreSQL support requires async PersistenceProvider (v7.0).',
117
+ );
118
+ }
119
+
120
+ transaction<T>(fn: () => T): T {
121
+ void fn;
122
+ throw new Error(
123
+ 'PostgresPersistenceProvider.transaction() is not yet implemented. ' +
124
+ 'Full PostgreSQL support requires async PersistenceProvider (v7.0).',
125
+ );
126
+ }
127
+
128
+ ftsSearch<T = Record<string, unknown>>(
129
+ table: string,
130
+ query: string,
131
+ options?: FtsSearchOptions,
132
+ ): T[] {
133
+ const _cols = options?.columns?.length ? options.columns.join(', ') : '*';
134
+ const _limit = options?.limit ?? 50;
135
+ const _offset = options?.offset ?? 0;
136
+ void table;
137
+ void query;
138
+ // Would generate: SELECT cols FROM table WHERE tsvector_col @@ to_tsquery($1)
139
+ // ORDER BY ts_rank(tsvector_col, to_tsquery($1)) DESC LIMIT $2 OFFSET $3
140
+ throw new Error(
141
+ 'PostgresPersistenceProvider.ftsSearch() is not yet implemented. ' +
142
+ 'Full PostgreSQL support requires async PersistenceProvider (v7.0).',
143
+ );
144
+ }
145
+
146
+ ftsRebuild(table: string): void {
147
+ // Would run: REINDEX INDEX idx_{table}_fts
148
+ void table;
149
+ }
150
+
151
+ close(): void {
152
+ if (this.pool) {
153
+ // Fire-and-forget pool end; sync interface constraint
154
+ void (this.pool.end() as Promise<void>);
155
+ }
156
+ }
157
+ }