@enterpriseai/cli 3.19.0 → 3.19.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (364) hide show
  1. package/package.json +3 -3
  2. package/resources/deploy/eai-app-template/producer-pin.json +5 -5
  3. package/resources/deploy/eai-app-template/tests/source-unknown-deployment-evidence.test.mjs +2141 -1
  4. package/resources/gofer/.gofer-version +4 -357
  5. package/resources/gofer/agents-skills/eai/SKILL.md +365 -0
  6. package/resources/gofer/agents-skills/eai-update/SKILL.md +56 -0
  7. package/resources/gofer/agents-skills/gofer-documentation/SKILL.md +93 -0
  8. package/resources/gofer/bash-scripts/create-new-feature.sh +3 -1
  9. package/resources/gofer/bash-scripts/sync-implementation-status.sh +4 -3
  10. package/resources/gofer/bash-scripts/update-agent-context.sh +7 -8
  11. package/resources/gofer/bash-scripts/validate-artifact.sh +10 -6
  12. package/resources/gofer/bash-scripts/verify-object-type-routing-workspace.sh +184 -0
  13. package/resources/gofer/claude-commands/eai-update.md +46 -0
  14. package/resources/gofer/claude-commands/eai.md +355 -0
  15. package/resources/gofer/claude-skills/eai/SKILL.md +365 -0
  16. package/resources/gofer/claude-skills/eai-update/SKILL.md +56 -0
  17. package/resources/gofer/claude-skills/gofer-documentation/SKILL.md +93 -0
  18. package/resources/gofer/commands/0_gofer_start.md +440 -99
  19. package/resources/gofer/commands/0a_problem_validation.md +187 -27
  20. package/resources/gofer/commands/10_gofer_cloud.md +183 -23
  21. package/resources/gofer/commands/1_gofer_research.md +239 -30
  22. package/resources/gofer/commands/2_gofer_specify.md +239 -35
  23. package/resources/gofer/commands/3_gofer_plan.md +259 -43
  24. package/resources/gofer/commands/4_gofer_tasks.md +401 -81
  25. package/resources/gofer/commands/5_gofer_implement.md +375 -84
  26. package/resources/gofer/commands/6_gofer_validate.md +284 -53
  27. package/resources/gofer/commands/7_gofer_save.md +187 -26
  28. package/resources/gofer/commands/7a_stakeholder_comms.md +183 -23
  29. package/resources/gofer/commands/8_gofer_branding.md +183 -23
  30. package/resources/gofer/commands/9_gofer_tests.md +212 -23
  31. package/resources/gofer/commands/gofer_bootstrap_workspace.md +121 -8
  32. package/resources/gofer/commands/gofer_check_workspace.md +123 -8
  33. package/resources/gofer/commands/gofer_constitution.md +183 -23
  34. package/resources/gofer/commands/gofer_diagnose.md +183 -23
  35. package/resources/gofer/commands/gofer_eai_first_run.md +254 -25
  36. package/resources/gofer/commands/gofer_hydrate.md +183 -23
  37. package/resources/gofer/commands/gofer_personality.md +117 -6
  38. package/resources/gofer/commands/gofer_plan.md +117 -6
  39. package/resources/gofer/commands/gofer_side.md +117 -6
  40. package/resources/gofer/commands/gofer_spec_summary.md +183 -23
  41. package/resources/gofer/commands/gofer_tdd.md +183 -23
  42. package/resources/gofer/commands/gofer_vocabulary.md +185 -25
  43. package/resources/gofer/commands/gofer_zoom_out.md +183 -23
  44. package/resources/gofer/config/typesafe-learning-review.json +16 -0
  45. package/resources/gofer/config/typesafe-semantic-review.json +9 -0
  46. package/resources/gofer/copilot-instructions/gofer-cost.instructions.md +15 -6
  47. package/resources/gofer/copilot-instructions/typescript.instructions.md +1 -1
  48. package/resources/gofer/copilot-prompts/eai-update.prompt.md +58 -0
  49. package/resources/gofer/copilot-prompts/eai.prompt.md +367 -0
  50. package/resources/gofer/gemini/commands/gofer/eai-update.md +22 -0
  51. package/resources/gofer/gemini/commands/gofer/eai-update.toml +2 -0
  52. package/resources/gofer/gemini/commands/gofer/eai.md +357 -0
  53. package/resources/gofer/gemini/commands/gofer/eai.toml +2 -0
  54. package/resources/gofer/gemini/commands/gofer/manifest.json +3 -27
  55. package/resources/gofer/gemini/extension.json +4 -4
  56. package/resources/gofer/github-agents/eai.agent.md +83 -0
  57. package/resources/gofer/github-agents/gofer-business.agent.md +73 -4
  58. package/resources/gofer/github-agents/gofer-implement.agent.md +71 -2
  59. package/resources/gofer/github-agents/gofer-plan.agent.md +72 -2
  60. package/resources/gofer/github-agents/gofer-research.agent.md +72 -2
  61. package/resources/gofer/github-agents/gofer-validate.agent.md +72 -2
  62. package/resources/gofer/github-skills/eai/SKILL.md +365 -0
  63. package/resources/gofer/github-skills/eai-update/SKILL.md +56 -0
  64. package/resources/gofer/github-skills/gofer-documentation/SKILL.md +93 -0
  65. package/resources/gofer/grok-skills/eai/SKILL.md +267 -14
  66. package/resources/gofer/grok-skills/eai-update/SKILL.md +56 -0
  67. package/resources/gofer/hook-scripts/user-prompt-submit.mjs +58 -13
  68. package/resources/gofer/instruction-templates/base/agents-base.md +8 -4
  69. package/resources/gofer/instruction-templates/gofer/gofer-claude.md +7 -5
  70. package/resources/gofer/instruction-templates/gofer/gofer-copilot.md +10 -8
  71. package/resources/gofer/memory/constitution.md +9 -7
  72. package/resources/gofer/memory/gofer-model-policy.yaml +23 -1
  73. package/resources/gofer/memory/lessons.md +0 -31
  74. package/resources/gofer/node-scripts/canonical-descriptions.mjs +1 -1
  75. package/resources/gofer/node-scripts/check-version-alignment.mjs +4 -7
  76. package/resources/gofer/node-scripts/delivery-lineage-corpus.mjs +156 -0
  77. package/resources/gofer/node-scripts/generate-commands.mjs +1073 -265
  78. package/resources/gofer/node-scripts/gofer-acceptance-check.mjs +143 -0
  79. package/resources/gofer/node-scripts/gofer-agent-catalog.mjs +271 -0
  80. package/resources/gofer/node-scripts/gofer-benchmark-executor.mjs +123 -0
  81. package/resources/gofer/node-scripts/gofer-benchmark-sign.mjs +62 -0
  82. package/resources/gofer/node-scripts/gofer-benchmark-signer.mjs +89 -0
  83. package/resources/gofer/node-scripts/gofer-benchmark.mjs +76 -0
  84. package/resources/gofer/node-scripts/gofer-blocker-control.mjs +274 -0
  85. package/resources/gofer/node-scripts/gofer-claude-adapter.mjs +154 -0
  86. package/resources/gofer/node-scripts/gofer-closed-loop-audit.mjs +19 -2
  87. package/resources/gofer/node-scripts/gofer-delivery-check.mjs +121 -0
  88. package/resources/gofer/node-scripts/gofer-encrypted-key.mjs +58 -0
  89. package/resources/gofer/node-scripts/gofer-execution-metrics.mjs +477 -0
  90. package/resources/gofer/node-scripts/gofer-execution-recovery.mjs +334 -0
  91. package/resources/gofer/node-scripts/gofer-grok-adapter.mjs +184 -0
  92. package/resources/gofer/node-scripts/gofer-heldout-corpus.mjs +73 -0
  93. package/resources/gofer/node-scripts/gofer-heldout-snapshot.mjs +286 -0
  94. package/resources/gofer/node-scripts/gofer-heldout-verifier.mjs +211 -0
  95. package/resources/gofer/node-scripts/gofer-host-capability.mjs +335 -0
  96. package/resources/gofer/node-scripts/gofer-independent-reviewer.mjs +130 -0
  97. package/resources/gofer/node-scripts/gofer-live-routing.mjs +109 -0
  98. package/resources/gofer/node-scripts/gofer-local-capability-issuer.mjs +71 -0
  99. package/resources/gofer/node-scripts/gofer-local-isolation.mjs +175 -0
  100. package/resources/gofer/node-scripts/gofer-local-settings-cleanup.mjs +388 -0
  101. package/resources/gofer/node-scripts/gofer-loop-audit.mjs +54 -3
  102. package/resources/gofer/node-scripts/gofer-native-adapter.mjs +567 -0
  103. package/resources/gofer/node-scripts/gofer-native-benchmark-dispatch.mjs +145 -0
  104. package/resources/gofer/node-scripts/gofer-native-runtime.mjs +169 -0
  105. package/resources/gofer/node-scripts/gofer-pre-edit-check.mjs +204 -0
  106. package/resources/gofer/node-scripts/gofer-priority-check.mjs +214 -0
  107. package/resources/gofer/node-scripts/gofer-response-check.mjs +56 -0
  108. package/resources/gofer/node-scripts/gofer-reviewed-learning.mjs +1232 -0
  109. package/resources/gofer/node-scripts/gofer-run-verified-task.mjs +330 -0
  110. package/resources/gofer/node-scripts/gofer-runtime-ledger.mjs +289 -0
  111. package/resources/gofer/node-scripts/gofer-semantic-drift.mjs +584 -0
  112. package/resources/gofer/node-scripts/gofer-surface-update.mjs +1178 -0
  113. package/resources/gofer/node-scripts/gofer-trust-bootstrap.mjs +77 -0
  114. package/resources/gofer/node-scripts/gofer-trusted-benchmark.mjs +102 -0
  115. package/resources/gofer/node-scripts/gofer-trusted-evaluator.mjs +274 -0
  116. package/resources/gofer/node-scripts/gofer-tty-prompt.mjs +34 -0
  117. package/resources/gofer/node-scripts/gofer-typesafe-credentials.mjs +435 -0
  118. package/resources/gofer/node-scripts/gofer-ui-preview.mjs +1212 -0
  119. package/resources/gofer/node-scripts/gofer-verified-execution.mjs +484 -0
  120. package/resources/gofer/node-scripts/gofer-verifier-key-ceremony.mjs +101 -0
  121. package/resources/gofer/node-scripts/gofer-worktree-layout.mjs +32 -0
  122. package/resources/gofer/node-scripts/object-type-routing-phase-bundle.mjs +360 -0
  123. package/resources/gofer/node-scripts/package-agent-plugin.mjs +574 -82
  124. package/resources/gofer/node-scripts/parse-stage-command.mjs +1 -1
  125. package/resources/gofer/node-scripts/render-delivery-lineage.mjs +178 -0
  126. package/resources/gofer/node-scripts/sync-extension-resources.mjs +78 -2
  127. package/resources/gofer/node-scripts/validate-object-type-identifiers.mjs +767 -0
  128. package/resources/gofer/node-scripts/validate-object-type-routing-workspace.mjs +1325 -0
  129. package/resources/gofer/node-scripts/validate-v4-resource-contract.d.mts +44 -0
  130. package/resources/gofer/node-scripts/validate-v4-resource-contract.mjs +957 -0
  131. package/resources/gofer/node-scripts/workspace-bootstrap-lib.mjs +685 -116
  132. package/resources/gofer/references/agent-catalog.json +126 -0
  133. package/resources/gofer/references/blocker-mediation.md +156 -0
  134. package/resources/gofer/references/business-updates-and-goal-checks.md +109 -0
  135. package/resources/gofer/references/delivery-lineage.md +48 -0
  136. package/resources/gofer/references/mvp-capability-validation.md +102 -0
  137. package/resources/gofer/references/platform/README.md +1 -1
  138. package/resources/gofer/references/platform/eai-app-template.md +15 -13
  139. package/resources/gofer/references/platform/eai-auth-access.md +104 -0
  140. package/resources/gofer/references/platform/eai-config-driven-ui.md +5 -5
  141. package/resources/gofer/references/platform/eai-error-catalog.yaml +77 -19
  142. package/resources/gofer/references/platform/eai-repo-contract.md +61 -23
  143. package/resources/gofer/references/platform/eai-service-patterns.md +102 -105
  144. package/resources/gofer/references/platform/eai.md +44 -8
  145. package/resources/gofer/references/platform/vertical-template.md +10 -9
  146. package/resources/gofer/references/priority-outcome-protection.md +152 -0
  147. package/resources/gofer/references/verified-agent-execution.md +179 -0
  148. package/resources/gofer/schemas/gofer-learning-trace-v1.json +70 -0
  149. package/resources/gofer/specify-commands/0_gofer_start.md +440 -99
  150. package/resources/gofer/specify-commands/0a_problem_validation.md +187 -27
  151. package/resources/gofer/specify-commands/10_gofer_cloud.md +183 -23
  152. package/resources/gofer/specify-commands/1_gofer_research.md +239 -30
  153. package/resources/gofer/specify-commands/2_gofer_specify.md +239 -35
  154. package/resources/gofer/specify-commands/3_gofer_plan.md +259 -43
  155. package/resources/gofer/specify-commands/4_gofer_tasks.md +401 -81
  156. package/resources/gofer/specify-commands/5_gofer_implement.md +375 -84
  157. package/resources/gofer/specify-commands/6_gofer_validate.md +284 -53
  158. package/resources/gofer/specify-commands/7_gofer_save.md +187 -26
  159. package/resources/gofer/specify-commands/7a_stakeholder_comms.md +183 -23
  160. package/resources/gofer/specify-commands/8_gofer_branding.md +183 -23
  161. package/resources/gofer/specify-commands/9_gofer_tests.md +212 -23
  162. package/resources/gofer/specify-commands/gofer_bootstrap_workspace.md +121 -8
  163. package/resources/gofer/specify-commands/gofer_check_workspace.md +123 -8
  164. package/resources/gofer/specify-commands/gofer_constitution.md +183 -23
  165. package/resources/gofer/specify-commands/gofer_diagnose.md +183 -23
  166. package/resources/gofer/specify-commands/gofer_eai_first_run.md +254 -25
  167. package/resources/gofer/specify-commands/gofer_hydrate.md +183 -23
  168. package/resources/gofer/specify-commands/gofer_personality.md +117 -6
  169. package/resources/gofer/specify-commands/gofer_plan.md +117 -6
  170. package/resources/gofer/specify-commands/gofer_side.md +117 -6
  171. package/resources/gofer/specify-commands/gofer_spec_summary.md +183 -23
  172. package/resources/gofer/specify-commands/gofer_tdd.md +183 -23
  173. package/resources/gofer/specify-commands/gofer_vocabulary.md +185 -25
  174. package/resources/gofer/specify-commands/gofer_zoom_out.md +183 -23
  175. package/resources/gofer/specify-config/object-type-routing.json +24 -0
  176. package/resources/gofer/specify-config/typesafe-learning-review.json +16 -0
  177. package/resources/gofer/specify-config/typesafe-semantic-review.json +9 -0
  178. package/resources/gofer/system-skills/eai/SKILL.md +365 -0
  179. package/resources/gofer/system-skills/eai-update/SKILL.md +56 -0
  180. package/resources/gofer/system-skills/gofer-documentation/SKILL.md +93 -0
  181. package/resources/gofer/templates/build-map-template.md +64 -0
  182. package/resources/gofer/templates/business-scenarios-template.json +14 -0
  183. package/resources/gofer/templates/ciso-security-summary-template.md +3 -3
  184. package/resources/gofer/templates/context-bundle-template.md +4 -3
  185. package/resources/gofer/templates/contract-pack-template.md +8 -8
  186. package/resources/gofer/templates/cto-architecture-summary-template.md +3 -3
  187. package/resources/gofer/templates/delivery-lineage-template.json +8 -0
  188. package/resources/gofer/templates/eai-preflight-template.md +109 -68
  189. package/resources/gofer/templates/goal-ledger-template.json +1 -1
  190. package/resources/gofer/templates/gofer-model-policy.yaml +28 -6
  191. package/resources/gofer/templates/loop-contract-template.json +3 -1
  192. package/resources/gofer/templates/plan-template.md +8 -7
  193. package/resources/gofer/templates/priority-plan-template.json +29 -0
  194. package/resources/gofer/templates/proposal-review-template.md +6 -3
  195. package/resources/gofer/templates/release-capability-ledger-template.md +24 -0
  196. package/resources/gofer/templates/research-template.md +7 -6
  197. package/resources/gofer/templates/spec-summary-template.md +1 -1
  198. package/resources/gofer/templates/spec-template.md +45 -6
  199. package/resources/gofer/templates/tasks-template.md +12 -3
  200. package/resources/gofer/templates/ui-preview-brief-template.md +55 -5
  201. package/resources/gofer/templates/ui-review-log-template.md +21 -3
  202. package/resources/gofer/templates/ui-show-and-tell-template.md +46 -0
  203. package/resources/gofer/templates/visuals/capability-heatmap-template.md +2 -2
  204. package/resources/gofer/templates/working-backwards-prfaq-template.md +9 -8
  205. package/resources/linked-sources.json +4 -4
  206. package/resources/gofer/agents-skills/0_gofer_start/SKILL.md +0 -1287
  207. package/resources/gofer/agents-skills/0a_problem_validation/SKILL.md +0 -429
  208. package/resources/gofer/agents-skills/10_gofer_cloud/SKILL.md +0 -635
  209. package/resources/gofer/agents-skills/1_gofer_research/SKILL.md +0 -1159
  210. package/resources/gofer/agents-skills/2_gofer_specify/SKILL.md +0 -860
  211. package/resources/gofer/agents-skills/3_gofer_plan/SKILL.md +0 -899
  212. package/resources/gofer/agents-skills/4_gofer_tasks/SKILL.md +0 -760
  213. package/resources/gofer/agents-skills/5_gofer_implement/SKILL.md +0 -944
  214. package/resources/gofer/agents-skills/6_gofer_validate/SKILL.md +0 -2302
  215. package/resources/gofer/agents-skills/7_gofer_save/SKILL.md +0 -423
  216. package/resources/gofer/agents-skills/7a_stakeholder_comms/SKILL.md +0 -508
  217. package/resources/gofer/agents-skills/8_gofer_branding/SKILL.md +0 -315
  218. package/resources/gofer/agents-skills/9_gofer_tests/SKILL.md +0 -558
  219. package/resources/gofer/agents-skills/gofer_bootstrap_workspace/SKILL.md +0 -148
  220. package/resources/gofer/agents-skills/gofer_check_workspace/SKILL.md +0 -119
  221. package/resources/gofer/agents-skills/gofer_constitution/SKILL.md +0 -612
  222. package/resources/gofer/agents-skills/gofer_diagnose/SKILL.md +0 -114
  223. package/resources/gofer/agents-skills/gofer_eai_first_run/SKILL.md +0 -397
  224. package/resources/gofer/agents-skills/gofer_hydrate/SKILL.md +0 -561
  225. package/resources/gofer/agents-skills/gofer_personality/SKILL.md +0 -44
  226. package/resources/gofer/agents-skills/gofer_plan/SKILL.md +0 -40
  227. package/resources/gofer/agents-skills/gofer_side/SKILL.md +0 -45
  228. package/resources/gofer/agents-skills/gofer_spec_summary/SKILL.md +0 -112
  229. package/resources/gofer/agents-skills/gofer_tdd/SKILL.md +0 -112
  230. package/resources/gofer/agents-skills/gofer_vocabulary/SKILL.md +0 -112
  231. package/resources/gofer/agents-skills/gofer_zoom_out/SKILL.md +0 -112
  232. package/resources/gofer/claude-commands/0_gofer_start.md +0 -1282
  233. package/resources/gofer/claude-commands/0a_problem_validation.md +0 -424
  234. package/resources/gofer/claude-commands/10_gofer_cloud.md +0 -630
  235. package/resources/gofer/claude-commands/1_gofer_research.md +0 -1154
  236. package/resources/gofer/claude-commands/2_gofer_specify.md +0 -855
  237. package/resources/gofer/claude-commands/3_gofer_plan.md +0 -894
  238. package/resources/gofer/claude-commands/4_gofer_tasks.md +0 -755
  239. package/resources/gofer/claude-commands/5_gofer_implement.md +0 -939
  240. package/resources/gofer/claude-commands/6_gofer_validate.md +0 -2297
  241. package/resources/gofer/claude-commands/7_gofer_save.md +0 -418
  242. package/resources/gofer/claude-commands/7a_stakeholder_comms.md +0 -503
  243. package/resources/gofer/claude-commands/8_gofer_branding.md +0 -310
  244. package/resources/gofer/claude-commands/9_gofer_tests.md +0 -553
  245. package/resources/gofer/claude-commands/gofer_bootstrap_workspace.md +0 -143
  246. package/resources/gofer/claude-commands/gofer_check_workspace.md +0 -114
  247. package/resources/gofer/claude-commands/gofer_constitution.md +0 -607
  248. package/resources/gofer/claude-commands/gofer_diagnose.md +0 -109
  249. package/resources/gofer/claude-commands/gofer_eai_first_run.md +0 -392
  250. package/resources/gofer/claude-commands/gofer_hydrate.md +0 -556
  251. package/resources/gofer/claude-commands/gofer_personality.md +0 -39
  252. package/resources/gofer/claude-commands/gofer_plan.md +0 -35
  253. package/resources/gofer/claude-commands/gofer_side.md +0 -40
  254. package/resources/gofer/claude-commands/gofer_spec_summary.md +0 -107
  255. package/resources/gofer/claude-commands/gofer_tdd.md +0 -107
  256. package/resources/gofer/claude-commands/gofer_vocabulary.md +0 -107
  257. package/resources/gofer/claude-commands/gofer_zoom_out.md +0 -107
  258. package/resources/gofer/claude-skills/eai-gofer/SKILL.md +0 -56
  259. package/resources/gofer/copilot-prompts/0_gofer_start.prompt.md +0 -1308
  260. package/resources/gofer/copilot-prompts/0a_problem_validation.prompt.md +0 -429
  261. package/resources/gofer/copilot-prompts/10_gofer_cloud.prompt.md +0 -644
  262. package/resources/gofer/copilot-prompts/1_gofer_research.prompt.md +0 -1177
  263. package/resources/gofer/copilot-prompts/2_gofer_specify.prompt.md +0 -878
  264. package/resources/gofer/copilot-prompts/3_gofer_plan.prompt.md +0 -917
  265. package/resources/gofer/copilot-prompts/4_gofer_tasks.prompt.md +0 -778
  266. package/resources/gofer/copilot-prompts/5_gofer_implement.prompt.md +0 -962
  267. package/resources/gofer/copilot-prompts/6_gofer_validate.prompt.md +0 -2310
  268. package/resources/gofer/copilot-prompts/7_gofer_save.prompt.md +0 -433
  269. package/resources/gofer/copilot-prompts/7a_stakeholder_comms.prompt.md +0 -516
  270. package/resources/gofer/copilot-prompts/8_gofer_branding.prompt.md +0 -323
  271. package/resources/gofer/copilot-prompts/9_gofer_tests.prompt.md +0 -566
  272. package/resources/gofer/copilot-prompts/gofer_bootstrap_workspace.prompt.md +0 -161
  273. package/resources/gofer/copilot-prompts/gofer_check_workspace.prompt.md +0 -132
  274. package/resources/gofer/copilot-prompts/gofer_constitution.prompt.md +0 -621
  275. package/resources/gofer/copilot-prompts/gofer_diagnose.prompt.md +0 -127
  276. package/resources/gofer/copilot-prompts/gofer_eai_first_run.prompt.md +0 -410
  277. package/resources/gofer/copilot-prompts/gofer_hydrate.prompt.md +0 -571
  278. package/resources/gofer/copilot-prompts/gofer_personality.prompt.md +0 -57
  279. package/resources/gofer/copilot-prompts/gofer_plan.prompt.md +0 -53
  280. package/resources/gofer/copilot-prompts/gofer_side.prompt.md +0 -58
  281. package/resources/gofer/copilot-prompts/gofer_spec_summary.prompt.md +0 -125
  282. package/resources/gofer/copilot-prompts/gofer_tdd.prompt.md +0 -125
  283. package/resources/gofer/copilot-prompts/gofer_vocabulary.prompt.md +0 -125
  284. package/resources/gofer/copilot-prompts/gofer_zoom_out.prompt.md +0 -125
  285. package/resources/gofer/gemini/commands/gofer/0_gofer_start.md +0 -1282
  286. package/resources/gofer/gemini/commands/gofer/0_gofer_start.toml +0 -2
  287. package/resources/gofer/gemini/commands/gofer/0a_problem_validation.md +0 -424
  288. package/resources/gofer/gemini/commands/gofer/0a_problem_validation.toml +0 -2
  289. package/resources/gofer/gemini/commands/gofer/10_gofer_cloud.md +0 -630
  290. package/resources/gofer/gemini/commands/gofer/10_gofer_cloud.toml +0 -2
  291. package/resources/gofer/gemini/commands/gofer/1_gofer_research.md +0 -1154
  292. package/resources/gofer/gemini/commands/gofer/1_gofer_research.toml +0 -2
  293. package/resources/gofer/gemini/commands/gofer/2_gofer_specify.md +0 -855
  294. package/resources/gofer/gemini/commands/gofer/2_gofer_specify.toml +0 -2
  295. package/resources/gofer/gemini/commands/gofer/3_gofer_plan.md +0 -894
  296. package/resources/gofer/gemini/commands/gofer/3_gofer_plan.toml +0 -2
  297. package/resources/gofer/gemini/commands/gofer/4_gofer_tasks.md +0 -755
  298. package/resources/gofer/gemini/commands/gofer/4_gofer_tasks.toml +0 -2
  299. package/resources/gofer/gemini/commands/gofer/5_gofer_implement.md +0 -939
  300. package/resources/gofer/gemini/commands/gofer/5_gofer_implement.toml +0 -2
  301. package/resources/gofer/gemini/commands/gofer/6_gofer_validate.md +0 -2297
  302. package/resources/gofer/gemini/commands/gofer/6_gofer_validate.toml +0 -2
  303. package/resources/gofer/gemini/commands/gofer/7_gofer_save.md +0 -418
  304. package/resources/gofer/gemini/commands/gofer/7_gofer_save.toml +0 -2
  305. package/resources/gofer/gemini/commands/gofer/7a_stakeholder_comms.md +0 -503
  306. package/resources/gofer/gemini/commands/gofer/7a_stakeholder_comms.toml +0 -2
  307. package/resources/gofer/gemini/commands/gofer/8_gofer_branding.md +0 -310
  308. package/resources/gofer/gemini/commands/gofer/8_gofer_branding.toml +0 -2
  309. package/resources/gofer/gemini/commands/gofer/9_gofer_tests.md +0 -553
  310. package/resources/gofer/gemini/commands/gofer/9_gofer_tests.toml +0 -2
  311. package/resources/gofer/gemini/commands/gofer/gofer_bootstrap_workspace.md +0 -143
  312. package/resources/gofer/gemini/commands/gofer/gofer_bootstrap_workspace.toml +0 -2
  313. package/resources/gofer/gemini/commands/gofer/gofer_check_workspace.md +0 -114
  314. package/resources/gofer/gemini/commands/gofer/gofer_check_workspace.toml +0 -2
  315. package/resources/gofer/gemini/commands/gofer/gofer_constitution.md +0 -607
  316. package/resources/gofer/gemini/commands/gofer/gofer_constitution.toml +0 -2
  317. package/resources/gofer/gemini/commands/gofer/gofer_diagnose.md +0 -109
  318. package/resources/gofer/gemini/commands/gofer/gofer_diagnose.toml +0 -2
  319. package/resources/gofer/gemini/commands/gofer/gofer_eai_first_run.md +0 -392
  320. package/resources/gofer/gemini/commands/gofer/gofer_eai_first_run.toml +0 -2
  321. package/resources/gofer/gemini/commands/gofer/gofer_hydrate.md +0 -556
  322. package/resources/gofer/gemini/commands/gofer/gofer_hydrate.toml +0 -2
  323. package/resources/gofer/gemini/commands/gofer/gofer_personality.md +0 -39
  324. package/resources/gofer/gemini/commands/gofer/gofer_personality.toml +0 -2
  325. package/resources/gofer/gemini/commands/gofer/gofer_plan.md +0 -35
  326. package/resources/gofer/gemini/commands/gofer/gofer_plan.toml +0 -2
  327. package/resources/gofer/gemini/commands/gofer/gofer_side.md +0 -40
  328. package/resources/gofer/gemini/commands/gofer/gofer_side.toml +0 -2
  329. package/resources/gofer/gemini/commands/gofer/gofer_spec_summary.md +0 -107
  330. package/resources/gofer/gemini/commands/gofer/gofer_spec_summary.toml +0 -2
  331. package/resources/gofer/gemini/commands/gofer/gofer_tdd.md +0 -107
  332. package/resources/gofer/gemini/commands/gofer/gofer_tdd.toml +0 -2
  333. package/resources/gofer/gemini/commands/gofer/gofer_vocabulary.md +0 -107
  334. package/resources/gofer/gemini/commands/gofer/gofer_vocabulary.toml +0 -2
  335. package/resources/gofer/gemini/commands/gofer/gofer_zoom_out.md +0 -107
  336. package/resources/gofer/gemini/commands/gofer/gofer_zoom_out.toml +0 -2
  337. package/resources/gofer/github-skills/eai-gofer/SKILL.md +0 -56
  338. package/resources/gofer/system-skills/0_gofer_start/SKILL.md +0 -1287
  339. package/resources/gofer/system-skills/0a_problem_validation/SKILL.md +0 -429
  340. package/resources/gofer/system-skills/10_gofer_cloud/SKILL.md +0 -635
  341. package/resources/gofer/system-skills/1_gofer_research/SKILL.md +0 -1159
  342. package/resources/gofer/system-skills/2_gofer_specify/SKILL.md +0 -860
  343. package/resources/gofer/system-skills/3_gofer_plan/SKILL.md +0 -899
  344. package/resources/gofer/system-skills/4_gofer_tasks/SKILL.md +0 -760
  345. package/resources/gofer/system-skills/5_gofer_implement/SKILL.md +0 -944
  346. package/resources/gofer/system-skills/6_gofer_validate/SKILL.md +0 -2302
  347. package/resources/gofer/system-skills/7_gofer_save/SKILL.md +0 -423
  348. package/resources/gofer/system-skills/7a_stakeholder_comms/SKILL.md +0 -508
  349. package/resources/gofer/system-skills/8_gofer_branding/SKILL.md +0 -315
  350. package/resources/gofer/system-skills/9_gofer_tests/SKILL.md +0 -558
  351. package/resources/gofer/system-skills/gofer_bootstrap_workspace/SKILL.md +0 -148
  352. package/resources/gofer/system-skills/gofer_check_workspace/SKILL.md +0 -119
  353. package/resources/gofer/system-skills/gofer_constitution/SKILL.md +0 -612
  354. package/resources/gofer/system-skills/gofer_diagnose/SKILL.md +0 -114
  355. package/resources/gofer/system-skills/gofer_eai_first_run/SKILL.md +0 -397
  356. package/resources/gofer/system-skills/gofer_hydrate/SKILL.md +0 -561
  357. package/resources/gofer/system-skills/gofer_personality/SKILL.md +0 -44
  358. package/resources/gofer/system-skills/gofer_plan/SKILL.md +0 -40
  359. package/resources/gofer/system-skills/gofer_side/SKILL.md +0 -45
  360. package/resources/gofer/system-skills/gofer_spec_summary/SKILL.md +0 -112
  361. package/resources/gofer/system-skills/gofer_tdd/SKILL.md +0 -112
  362. package/resources/gofer/system-skills/gofer_vocabulary/SKILL.md +0 -112
  363. package/resources/gofer/system-skills/gofer_zoom_out/SKILL.md +0 -112
  364. package/resources/gofer/templates/ui-approval-template.md +0 -32
@@ -0,0 +1,1232 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { createHash, sign, verify } from 'node:crypto';
4
+ import { constants } from 'node:fs';
5
+ import { promises as fs } from 'node:fs';
6
+ import path from 'node:path';
7
+ import process from 'node:process';
8
+ import { pathToFileURL } from 'node:url';
9
+ import { requestTypeSafeEvaluation, sharedCredentialConfigured } from './gofer-typesafe-credentials.mjs';
10
+ import { loadActiveBenchmarkVerifierKey, resolveTrustedEvaluatorPublicKey,
11
+ VERIFIER_EVALUATOR } from './gofer-trusted-evaluator.mjs';
12
+ import { promptHidden } from './gofer-tty-prompt.mjs';
13
+
14
+ const POLICY_PATH = path.join('.specify', 'config', 'typesafe-learning-review.json');
15
+ const STORE_PATH = path.join('.specify', 'memory', 'reviewed-learning');
16
+ const MAX_SOURCE_BYTES = 16 * 1024 * 1024;
17
+ const MAX_RECORDS = 100_000;
18
+ const MAX_LESSON_BYTES = 8 * 1024;
19
+ const OFFICIAL_ENDPOINT = 'https://api.typesafe.ai/v1/systemone';
20
+ const noFollowFlag = process.platform === 'win32' ? 0 : constants.O_NOFOLLOW;
21
+ const sensitiveKey = /(authorization|api[-_]?key|secret|token|password|credential|cookie)/i;
22
+ const secretPatterns = [
23
+ /\bBearer\s+[A-Za-z0-9._~+\/-]+=*/gi,
24
+ /\b(?:sk|ghp|github_pat|xox[baprs])[-_][A-Za-z0-9_-]{12,}\b/g,
25
+ /\bTYPESAFE_API_KEY\s*=\s*[^\s]+/gi,
26
+ /\b(?:api[-_]?key|access[-_]?token|password|secret|token)\s*=\s*[^\s&]+/gi,
27
+ ];
28
+ const verifiedDirectories = new Map();
29
+ const REVIEW_LOCK_TTL_MS = 5 * 60 * 1000;
30
+
31
+ function sha256(value) {
32
+ // This is a content-addressing digest, never a password derivation function.
33
+ // lgtm[js/insufficient-password-hash]
34
+ return createHash('sha256').update(value).digest('hex');
35
+ }
36
+
37
+ function stable(value) {
38
+ if (Array.isArray(value)) return value.map(stable);
39
+ if (value && typeof value === 'object') {
40
+ return Object.fromEntries(Object.keys(value).sort().map((key) => [key, stable(value[key])]));
41
+ }
42
+ return value;
43
+ }
44
+
45
+ function canonical(value) {
46
+ return JSON.stringify(stable(value));
47
+ }
48
+
49
+ function text(value) {
50
+ return typeof value === 'string' && value.trim() ? value.trim() : '';
51
+ }
52
+
53
+ function credentialSourceLabel(value) {
54
+ if (value === 'environment') return 'environment';
55
+ if (value === 'project_secret_file') return 'project_secret_file';
56
+ if (value === 'macos_keychain') return 'macos_keychain';
57
+ return 'none';
58
+ }
59
+
60
+ function validTime(value) {
61
+ const normalized = text(value);
62
+ if (!normalized || Number.isNaN(Date.parse(normalized))) return '';
63
+ return new Date(normalized).toISOString();
64
+ }
65
+
66
+ function truncateUtf8(value, maxBytes) {
67
+ const input = Buffer.from(String(value), 'utf8');
68
+ if (input.length <= maxBytes) return input.toString('utf8');
69
+ let end = maxBytes;
70
+ while (end > 0 && (input[end] & 0xc0) === 0x80) end -= 1;
71
+ return input.subarray(0, end).toString('utf8');
72
+ }
73
+
74
+ function redactString(value, maxBytes) {
75
+ let output = String(value);
76
+ for (const pattern of secretPatterns) output = output.replace(pattern, '[REDACTED]');
77
+ return truncateUtf8(output, maxBytes);
78
+ }
79
+
80
+ function redactValue(value, maxBytes, key = '', depth = 0) {
81
+ if (depth > 8) return '[TRUNCATED_DEPTH]';
82
+ if (sensitiveKey.test(key)) return '[REDACTED]';
83
+ if (typeof value === 'string') return redactString(value, maxBytes);
84
+ if (typeof value === 'number' || typeof value === 'boolean' || value === null) return value;
85
+ if (Array.isArray(value)) return value.slice(0, 100).map((item) => redactValue(item, maxBytes, '', depth + 1));
86
+ if (value && typeof value === 'object') {
87
+ return Object.fromEntries(
88
+ Object.entries(value).slice(0, 100).map(([name, item]) => [
89
+ redactString(name, 120),
90
+ redactValue(item, maxBytes, name, depth + 1),
91
+ ])
92
+ );
93
+ }
94
+ return String(value ?? '');
95
+ }
96
+
97
+ async function safeWorkspace(workspace) {
98
+ const root = path.resolve(workspace);
99
+ const info = await fs.lstat(root).catch(() => null);
100
+ if (!info || !info.isDirectory() || info.isSymbolicLink()) {
101
+ throw new Error('LEARNING_WORKSPACE_INVALID');
102
+ }
103
+ return root;
104
+ }
105
+
106
+ async function rejectSymlinkComponents(root, target) {
107
+ const relative = path.relative(root, target);
108
+ if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
109
+ throw new Error('LEARNING_PATH_OUTSIDE_WORKSPACE');
110
+ }
111
+ let current = root;
112
+ for (const component of relative.split(path.sep).filter(Boolean)) {
113
+ current = path.join(current, component);
114
+ const info = await fs.lstat(current).catch((error) => {
115
+ if (error?.code === 'ENOENT') return null;
116
+ throw error;
117
+ });
118
+ if (!info) return;
119
+ if (info.isSymbolicLink()) throw new Error('LEARNING_PATH_SYMLINK');
120
+ }
121
+ }
122
+
123
+ async function confined(workspace, input, { exists = false, regular = false } = {}) {
124
+ const root = await safeWorkspace(workspace);
125
+ const target = path.resolve(root, input);
126
+ await rejectSymlinkComponents(root, target);
127
+ const info = await fs.lstat(target).catch((error) => {
128
+ if (error?.code === 'ENOENT') return null;
129
+ throw error;
130
+ });
131
+ if (exists && !info) throw new Error('LEARNING_PATH_MISSING');
132
+ if (info?.isSymbolicLink()) throw new Error('LEARNING_PATH_SYMLINK');
133
+ if (regular && info && !info.isFile()) throw new Error('LEARNING_PATH_NOT_FILE');
134
+ return target;
135
+ }
136
+
137
+ async function holdDirectoryChain(root, target) {
138
+ const relativeParent = path.relative(root, path.dirname(target));
139
+ const directories = [root];
140
+ let current = root;
141
+ for (const component of relativeParent.split(path.sep).filter(Boolean)) {
142
+ current = path.join(current, component);
143
+ directories.push(current);
144
+ }
145
+ const held = [];
146
+ try {
147
+ for (const directory of directories) {
148
+ const handle = await fs.open(directory,
149
+ constants.O_RDONLY | (constants.O_DIRECTORY ?? 0) | noFollowFlag);
150
+ const opened = await handle.stat();
151
+ const currentInfo = await fs.lstat(directory);
152
+ if (!opened.isDirectory() || currentInfo.isSymbolicLink() ||
153
+ !sameIdentity(opened, currentInfo)) {
154
+ await handle.close();
155
+ throw new Error('LEARNING_STORE_PARENT_CHANGED');
156
+ }
157
+ held.push({ directory, handle, identity: opened });
158
+ }
159
+ return held;
160
+ } catch (error) {
161
+ await Promise.allSettled(held.map(({ handle }) => handle.close()));
162
+ throw error;
163
+ }
164
+ }
165
+
166
+ async function readBoundedFile(workspace, input, maxBytes = MAX_SOURCE_BYTES) {
167
+ const root = await safeWorkspace(workspace);
168
+ const target = await confined(root, input, { exists: true, regular: true });
169
+ const held = await holdDirectoryChain(root, target);
170
+ let handle;
171
+ try {
172
+ handle = await fs.open(target, constants.O_RDONLY | noFollowFlag);
173
+ const info = await handle.stat();
174
+ if (!info.isFile()) throw new Error('LEARNING_STORE_INVALID');
175
+ if (info.size > maxBytes) throw new Error('LEARNING_INPUT_TOO_LARGE');
176
+ const chunks = [];
177
+ let total = 0;
178
+ while (true) {
179
+ const chunk = Buffer.alloc(Math.min(64 * 1024, maxBytes - total + 1));
180
+ const { bytesRead } = await handle.read(chunk, 0, chunk.length, null);
181
+ if (!bytesRead) break;
182
+ total += bytesRead;
183
+ if (total > maxBytes) throw new Error('LEARNING_INPUT_TOO_LARGE');
184
+ chunks.push(chunk.subarray(0, bytesRead));
185
+ }
186
+ const content = Buffer.concat(chunks, total).toString('utf8');
187
+ for (const entry of held) {
188
+ const current = await fs.lstat(entry.directory);
189
+ if (current.isSymbolicLink() || !sameIdentity(entry.identity, current)) {
190
+ throw new Error('LEARNING_STORE_PARENT_CHANGED');
191
+ }
192
+ }
193
+ return { target, content };
194
+ } finally {
195
+ if (handle) await handle.close();
196
+ await Promise.allSettled(held.map(({ handle: directoryHandle }) => directoryHandle.close()));
197
+ }
198
+ }
199
+
200
+ async function ensureStore(workspace) {
201
+ const root = await safeWorkspace(workspace);
202
+ let current = root;
203
+ for (const component of STORE_PATH.split(path.sep)) {
204
+ const parentHandle = await fs.open(current,
205
+ constants.O_RDONLY | (constants.O_DIRECTORY ?? 0) | noFollowFlag);
206
+ try {
207
+ const openedParent = await parentHandle.stat();
208
+ const parentAfterOpen = await fs.lstat(current);
209
+ if (!openedParent.isDirectory() || parentAfterOpen.isSymbolicLink() ||
210
+ !sameIdentity(openedParent, parentAfterOpen)) {
211
+ throw new Error('LEARNING_STORE_PARENT_CHANGED');
212
+ }
213
+ const next = path.join(current, component);
214
+ await fs.mkdir(next, { mode: 0o700 }).catch((error) => {
215
+ if (error?.code !== 'EEXIST') throw error;
216
+ });
217
+ const parentAfter = await fs.lstat(current);
218
+ if (!sameIdentity(openedParent, parentAfter)) {
219
+ throw new Error('LEARNING_STORE_PATH_INVALID');
220
+ }
221
+ // The parent remains open, and the child is opened no-follow then validated by identity.
222
+ // codeql[js/file-system-race]
223
+ const childHandle = await fs.open(next,
224
+ constants.O_RDONLY | (constants.O_DIRECTORY ?? 0) | noFollowFlag).catch((error) => {
225
+ if (['ELOOP', 'ENOTDIR'].includes(error?.code)) throw new Error('LEARNING_PATH_SYMLINK');
226
+ throw error;
227
+ });
228
+ try {
229
+ const openedChild = await childHandle.stat();
230
+ const child = await fs.lstat(next);
231
+ if (!openedChild.isDirectory() || child.isSymbolicLink() ||
232
+ !sameIdentity(child, openedChild)) {
233
+ throw new Error('LEARNING_STORE_PATH_INVALID');
234
+ }
235
+ await childHandle.chmod(0o700);
236
+ verifiedDirectories.set(next, { dev: openedChild.dev, ino: openedChild.ino });
237
+ } finally {
238
+ await childHandle.close();
239
+ }
240
+ current = next;
241
+ } finally {
242
+ await parentHandle.close();
243
+ }
244
+ }
245
+ return current;
246
+ }
247
+
248
+ async function withPrivateDirectory(parent, name, action) {
249
+ await verifyRememberedDirectory(parent);
250
+ const parentHandle = await fs.open(parent,
251
+ constants.O_RDONLY | (constants.O_DIRECTORY ?? 0) | noFollowFlag);
252
+ const parentIdentity = await parentHandle.stat();
253
+ const target = path.join(parent, name);
254
+ try {
255
+ await fs.mkdir(target, { mode: 0o700 }).catch((error) => {
256
+ if (error?.code !== 'EEXIST') throw error;
257
+ });
258
+ await verifyRememberedDirectory(parent);
259
+ const handle = await fs.open(target,
260
+ constants.O_RDONLY | (constants.O_DIRECTORY ?? 0) | noFollowFlag).catch((error) => {
261
+ if (['ELOOP', 'ENOTDIR'].includes(error?.code)) throw new Error('LEARNING_STORE_PATH_INVALID');
262
+ throw error;
263
+ });
264
+ try {
265
+ const opened = await handle.stat();
266
+ const current = await fs.lstat(target);
267
+ const currentParent = await fs.lstat(parent);
268
+ if (!opened.isDirectory() || current.isSymbolicLink() || !sameIdentity(opened, current) ||
269
+ currentParent.isSymbolicLink() || !sameIdentity(parentIdentity, currentParent)) {
270
+ throw new Error('LEARNING_STORE_PATH_INVALID');
271
+ }
272
+ await handle.chmod(0o700);
273
+ verifiedDirectories.set(target, { dev: opened.dev, ino: opened.ino });
274
+ const result = await action(target);
275
+ const [parentAfterAction, targetAfterAction] = await Promise.all([
276
+ fs.lstat(parent),
277
+ fs.lstat(target),
278
+ ]);
279
+ if (parentAfterAction.isSymbolicLink() || targetAfterAction.isSymbolicLink() ||
280
+ !sameIdentity(parentIdentity, parentAfterAction) ||
281
+ !sameIdentity(opened, targetAfterAction)) {
282
+ throw new Error('LEARNING_STORE_PATH_INVALID');
283
+ }
284
+ return result;
285
+ } finally {
286
+ await handle.close();
287
+ }
288
+ } finally {
289
+ await parentHandle.close();
290
+ }
291
+ }
292
+
293
+ function sameIdentity(left, right) {
294
+ if (left.dev === undefined || left.ino === undefined ||
295
+ right.dev === undefined || right.ino === undefined) return true;
296
+ return left.dev === right.dev && left.ino === right.ino;
297
+ }
298
+
299
+ async function withVerifiedParent(target, action) {
300
+ const parent = path.dirname(target);
301
+ const handle = await fs.open(parent,
302
+ constants.O_RDONLY | (constants.O_DIRECTORY ?? 0) | noFollowFlag);
303
+ const opened = await handle.stat();
304
+ try {
305
+ const current = await fs.lstat(parent);
306
+ if (!opened.isDirectory() || current.isSymbolicLink() || !sameIdentity(opened, current)) {
307
+ throw new Error('LEARNING_STORE_PARENT_CHANGED');
308
+ }
309
+ const expected = verifiedDirectories.get(parent);
310
+ if (expected && !sameIdentity(expected, opened)) throw new Error('LEARNING_STORE_PARENT_CHANGED');
311
+ const result = await action();
312
+ const after = await fs.lstat(parent);
313
+ if (after.isSymbolicLink() || !sameIdentity(opened, after)) {
314
+ throw new Error('LEARNING_STORE_PARENT_CHANGED');
315
+ }
316
+ return result;
317
+ } finally {
318
+ await handle.close();
319
+ }
320
+ }
321
+
322
+ async function verifyRememberedDirectory(target) {
323
+ await withVerifiedParent(path.join(target, '.guard'), async () => undefined);
324
+ const current = await fs.lstat(target);
325
+ const expected = verifiedDirectories.get(target);
326
+ if (current.isSymbolicLink() || !current.isDirectory() ||
327
+ (expected && !sameIdentity(expected, current))) {
328
+ throw new Error('LEARNING_STORE_PATH_INVALID');
329
+ }
330
+ }
331
+
332
+ async function existingRegularFile(target) {
333
+ const info = await fs.lstat(target).catch((error) => {
334
+ if (error?.code === 'ENOENT') return null;
335
+ throw error;
336
+ });
337
+ if (info && (info.isSymbolicLink() || !info.isFile())) {
338
+ throw new Error('LEARNING_STORE_INVALID');
339
+ }
340
+ return info;
341
+ }
342
+
343
+ async function appendPrivate(target, value) {
344
+ return withVerifiedParent(target, async () => {
345
+ const beforeOpen = await existingRegularFile(target);
346
+ const handle = await fs.open(
347
+ target,
348
+ constants.O_WRONLY | constants.O_CREAT | constants.O_APPEND | noFollowFlag,
349
+ 0o600
350
+ );
351
+ try {
352
+ const info = await handle.stat();
353
+ if (!info.isFile() || (beforeOpen && !sameIdentity(beforeOpen, info))) {
354
+ throw new Error('LEARNING_STORE_INVALID');
355
+ }
356
+ await handle.writeFile(`${JSON.stringify(value)}\n`);
357
+ await handle.sync();
358
+ } finally {
359
+ await handle.close();
360
+ }
361
+ });
362
+ }
363
+
364
+ async function writePrivateExclusive(target, value) {
365
+ return withVerifiedParent(target, async () => {
366
+ let handle;
367
+ try {
368
+ await existingRegularFile(target);
369
+ handle = await fs.open(
370
+ target,
371
+ constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | noFollowFlag,
372
+ 0o600
373
+ );
374
+ await handle.writeFile(value);
375
+ await handle.sync();
376
+ } catch (error) {
377
+ if (error?.code !== 'EEXIST') throw error;
378
+ const beforeOpen = await existingRegularFile(target);
379
+ const existingHandle = await fs.open(target, constants.O_RDONLY | noFollowFlag);
380
+ try {
381
+ const info = await existingHandle.stat();
382
+ if (!info.isFile() || !beforeOpen || !sameIdentity(beforeOpen, info)) {
383
+ throw new Error('LEARNING_STORE_INVALID');
384
+ }
385
+ const existing = await existingHandle.readFile('utf8');
386
+ if (existing !== value) throw new Error('LEARNING_RECORD_COLLISION');
387
+ } finally {
388
+ await existingHandle.close();
389
+ }
390
+ } finally {
391
+ if (handle) await handle.close();
392
+ }
393
+ });
394
+ }
395
+
396
+ async function readJsonLines(target) {
397
+ return withVerifiedParent(target, async () => {
398
+ const beforeOpen = await existingRegularFile(target);
399
+ if (!beforeOpen) return [];
400
+ const handle = await fs.open(target, constants.O_RDONLY | noFollowFlag).catch((error) => {
401
+ if (error?.code === 'ENOENT') return null;
402
+ throw error;
403
+ });
404
+ if (!handle) return [];
405
+ try {
406
+ const info = await handle.stat();
407
+ const current = await fs.lstat(target);
408
+ if (!info.isFile() || current.isSymbolicLink() || !current.isFile() ||
409
+ !sameIdentity(beforeOpen, info) || !sameIdentity(info, current) ||
410
+ info.size > MAX_SOURCE_BYTES) throw new Error('LEARNING_STORE_INVALID');
411
+ const lines = (await handle.readFile('utf8')).split('\n').filter(Boolean);
412
+ if (lines.length > MAX_RECORDS) throw new Error('LEARNING_STORE_LIMIT');
413
+ return lines.map((line) => JSON.parse(line));
414
+ } finally {
415
+ await handle.close();
416
+ }
417
+ });
418
+ }
419
+
420
+ function eventType(name) {
421
+ if (['started', 'resumed', 'call_reserved', 'attempt_reserved', 'lease_granted'].includes(name)) return 'lifecycle';
422
+ if (['ledger_authorized', 'commit_authorized', 'approval'].includes(name)) return 'approval';
423
+ if (['check', 'verified'].includes(name)) return 'validation';
424
+ if (['blocked', 'cancelled', 'stale', 'repair_required', 'failed', 'error'].includes(name)) return 'error';
425
+ if (/command|shell/.test(name)) return 'command';
426
+ if (/file|patch|write|edit/.test(name)) return 'file_change';
427
+ if (/tool/.test(name)) return 'tool';
428
+ if (/message|prompt|response/.test(name)) return 'message';
429
+ return 'other';
430
+ }
431
+
432
+ function validateTrace(trace) {
433
+ if (trace?.schemaVersion !== 1 || !/^trace_[a-f0-9]{32}$/.test(trace.traceId ?? '') ||
434
+ !/^project_[a-f0-9]{24}$/.test(trace.projectId ?? '') || !text(trace.host) ||
435
+ !text(trace.sessionId) || !text(trace.objective) || !validTime(trace.startedAt) ||
436
+ !validTime(trace.endedAt) || !Array.isArray(trace.events) ||
437
+ trace.events.length < 1 || trace.events.length > 100 ||
438
+ !['host-event-export', 'verified-execution-journal'].includes(trace.source?.kind) ||
439
+ !/^[a-f0-9]{64}$/.test(trace.source?.sha256 ?? '') ||
440
+ Object.keys(trace.source ?? {}).sort().join(',') !== 'kind,sha256' ||
441
+ !/^[a-f0-9]{64}$/.test(trace.traceHash ?? '')) {
442
+ throw new Error('LEARNING_TRACE_INVALID');
443
+ }
444
+ for (const [index, event] of trace.events.entries()) {
445
+ if (event.sequence !== index + 1 || !/^event_[a-f0-9]{24}$/.test(event.id ?? '') ||
446
+ !validTime(event.time) || !text(event.name) || !text(event.type) ||
447
+ !event.data || typeof event.data !== 'object' || Array.isArray(event.data)) {
448
+ throw new Error('LEARNING_TRACE_EVENT_INVALID');
449
+ }
450
+ }
451
+ const { traceHash, ...withoutHash } = trace;
452
+ if (sha256(canonical(withoutHash)) !== traceHash) throw new Error('LEARNING_TRACE_HASH_INVALID');
453
+ return trace;
454
+ }
455
+
456
+ export function normalizeTrace({
457
+ workspace,
458
+ host,
459
+ objective,
460
+ sessionId,
461
+ sourceKind = 'host-event-export',
462
+ sourceContent,
463
+ events,
464
+ maxEvents = 100,
465
+ maxTextBytes = 2000,
466
+ }) {
467
+ if (!text(workspace) || !text(host) || !text(objective) || !text(sourceContent) ||
468
+ !Array.isArray(events) || events.length < 1 || events.length > maxEvents ||
469
+ !Number.isSafeInteger(maxEvents) || maxEvents < 1 || maxEvents > 100 ||
470
+ !Number.isSafeInteger(maxTextBytes) || maxTextBytes < 128 || maxTextBytes > 8192) {
471
+ throw new Error('LEARNING_NORMALIZE_INVALID');
472
+ }
473
+ const sourceHash = sha256(sourceContent);
474
+ const times = events.map((event) => validTime(event.time ?? event.timestamp ?? event.at)).filter(Boolean);
475
+ if (times.length !== events.length) throw new Error('LEARNING_EVENT_TIME_REQUIRED');
476
+ const projectId = `project_${sha256(path.resolve(workspace)).slice(0, 24)}`;
477
+ const resolvedSession = truncateUtf8(text(sessionId) || sourceHash.slice(0, 32), 200);
478
+ const normalizedEvents = events.map((event, index) => {
479
+ const name = redactString(text(event.event ?? event.name ?? event.type) || 'event', 120);
480
+ const data = redactValue(
481
+ Object.fromEntries(Object.entries(event).filter(([key]) => !['time', 'timestamp', 'at', 'event', 'name', 'type'].includes(key))),
482
+ maxTextBytes
483
+ );
484
+ const base = { sequence: index + 1, time: times[index], type: eventType(name), name, data };
485
+ return { id: `event_${sha256(canonical({ sourceHash, ...base })).slice(0, 24)}`, ...base };
486
+ });
487
+ const core = {
488
+ schemaVersion: 1,
489
+ projectId,
490
+ host: truncateUtf8(text(host), 64),
491
+ sessionId: resolvedSession,
492
+ objective: redactString(objective, 4000),
493
+ startedAt: times[0],
494
+ endedAt: times[times.length - 1],
495
+ source: { kind: sourceKind, sha256: sourceHash },
496
+ events: normalizedEvents,
497
+ };
498
+ const traceId = `trace_${sha256(canonical(core)).slice(0, 32)}`;
499
+ const withoutHash = { schemaVersion: 1, traceId, ...core };
500
+ return validateTrace({ ...withoutHash, traceHash: sha256(canonical(withoutHash)) });
501
+ }
502
+
503
+ export async function normalizeJournal({
504
+ workspace = process.cwd(),
505
+ input,
506
+ host,
507
+ objective,
508
+ sessionId,
509
+ sourceKind = 'verified-execution-journal',
510
+ }) {
511
+ const root = await safeWorkspace(workspace);
512
+ const { content } = await readBoundedFile(root, input);
513
+ const lines = content.split('\n').filter(Boolean);
514
+ if (!lines.length || lines.length > 100) throw new Error('LEARNING_EVENT_LIMIT');
515
+ const events = lines.map((line) => JSON.parse(line));
516
+ const trace = normalizeTrace({
517
+ workspace: root,
518
+ host,
519
+ objective,
520
+ sessionId,
521
+ sourceKind,
522
+ sourceContent: content,
523
+ events,
524
+ });
525
+ const store = await ensureStore(root);
526
+ const tracePath = await withPrivateDirectory(store, 'traces', async (traceDir) => {
527
+ const target = path.join(traceDir, `${trace.traceId}.json`);
528
+ await writePrivateExclusive(target, `${JSON.stringify(trace, null, 2)}\n`);
529
+ return target;
530
+ });
531
+ return { trace, tracePath };
532
+ }
533
+
534
+ async function loadPolicy(workspace) {
535
+ const { content } = await readBoundedFile(workspace, POLICY_PATH, 64 * 1024);
536
+ const policy = JSON.parse(content);
537
+ const thresholds = policy.thresholds;
538
+ if (policy.schemaVersion !== 1 || policy.provider !== 'typesafe' ||
539
+ policy.endpoint !== OFFICIAL_ENDPOINT || policy.model !== 'jev-latest' ||
540
+ typeof policy.enabled !== 'boolean' || !Number.isSafeInteger(policy.timeoutMs) ||
541
+ policy.timeoutMs < 1000 || policy.timeoutMs > 30_000 ||
542
+ !Number.isSafeInteger(policy.maxEvents) || policy.maxEvents < 1 || policy.maxEvents > 100 ||
543
+ !Number.isSafeInteger(policy.maxTextBytes) || policy.maxTextBytes < 128 || policy.maxTextBytes > 8192 ||
544
+ !Number.isSafeInteger(policy.maxProjectionBytes) || policy.maxProjectionBytes < 4096 ||
545
+ policy.maxProjectionBytes > 1024 * 1024 || !thresholds ||
546
+ ['taskSuccess', 'reusableLesson', 'evidenceSupport'].some((key) =>
547
+ typeof thresholds[key] !== 'number' || thresholds[key] < 0 || thresholds[key] > 1)) {
548
+ throw new Error('LEARNING_POLICY_INVALID');
549
+ }
550
+ return policy;
551
+ }
552
+
553
+ function proposedLesson(value) {
554
+ const title = truncateUtf8(text(value?.title), 200);
555
+ const lesson = truncateUtf8(text(value?.lesson), MAX_LESSON_BYTES);
556
+ const kind = truncateUtf8(text(value?.kind) || 'engineering-pattern', 80);
557
+ if (!title || !lesson) throw new Error('LEARNING_LESSON_REQUIRED');
558
+ return { title, lesson, kind };
559
+ }
560
+
561
+ export function buildEvaluationProjection(trace, proposal, policy) {
562
+ validateTrace(trace);
563
+ const boundedProposal = proposedLesson(proposal);
564
+ const safeProposal = redactValue(boundedProposal, policy.maxTextBytes);
565
+ const projection = {
566
+ schemaVersion: 1,
567
+ trace: {
568
+ traceId: trace.traceId,
569
+ projectId: trace.projectId,
570
+ host: redactString(trace.host, 200),
571
+ objective: redactString(trace.objective, policy.maxTextBytes),
572
+ startedAt: trace.startedAt,
573
+ endedAt: trace.endedAt,
574
+ source: { kind: trace.source.kind, sha256: trace.source.sha256 },
575
+ events: trace.events.slice(0, policy.maxEvents).map((event) => ({
576
+ id: event.id,
577
+ sequence: event.sequence,
578
+ time: event.time,
579
+ type: event.type,
580
+ name: redactString(event.name, 200),
581
+ data: redactValue(event.data, policy.maxTextBytes),
582
+ })),
583
+ },
584
+ proposedMemory: safeProposal,
585
+ };
586
+ const serialized = canonical(projection);
587
+ if (Buffer.byteLength(serialized, 'utf8') > policy.maxProjectionBytes) {
588
+ throw new Error('LEARNING_PROJECTION_TOO_LARGE');
589
+ }
590
+ return { projection, projectionHash: sha256(serialized), proposal: safeProposal };
591
+ }
592
+
593
+ function probability(answer) {
594
+ const direct = Number(answer?.noul ?? answer?.probability ?? answer?.score);
595
+ if (Number.isFinite(direct) && direct >= 0 && direct <= 1) return direct;
596
+ const choice = String(answer?.choice ?? '').toLowerCase();
597
+ const confidence = Number(answer?.confidence);
598
+ if (['yes', 'true'].includes(choice) && Number.isFinite(confidence) && confidence >= 0 && confidence <= 1) {
599
+ return confidence;
600
+ }
601
+ if (['no', 'false'].includes(choice) && Number.isFinite(confidence) && confidence >= 0 && confidence <= 1) {
602
+ return 1 - confidence;
603
+ }
604
+ return 0;
605
+ }
606
+
607
+ function answerMap(payload) {
608
+ if (payload?.answers && typeof payload.answers === 'object') return payload.answers;
609
+ if (payload?.questions && typeof payload.questions === 'object') return payload.questions;
610
+ if (payload?.results && typeof payload.results === 'object') return payload.results;
611
+ return {};
612
+ }
613
+
614
+ export async function evaluateTrace({
615
+ workspace = process.cwd(),
616
+ trace,
617
+ proposal,
618
+ dryRun = false,
619
+ fetchImpl = globalThis.fetch,
620
+ env = process.env,
621
+ keychain,
622
+ }) {
623
+ const root = await safeWorkspace(workspace);
624
+ const policy = await loadPolicy(root);
625
+ const expectedProjectId = `project_${sha256(root).slice(0, 24)}`;
626
+ if (trace?.projectId !== expectedProjectId) throw new Error('LEARNING_TRACE_PROJECT_MISMATCH');
627
+ const { projection, projectionHash, proposal: boundedProposal } =
628
+ buildEvaluationProjection(trace, proposal, policy);
629
+ const rubric = {
630
+ task_success: {
631
+ type: 'noul',
632
+ instructions: 'Did this trace complete the stated engineering objective successfully?',
633
+ },
634
+ reusable_lesson: {
635
+ type: 'noul',
636
+ instructions: 'Is the proposed lesson reusable for a future engineering task?',
637
+ },
638
+ evidence_support: {
639
+ type: 'noul',
640
+ instructions: 'Is the proposed lesson supported by concrete evidence in this trace?',
641
+ },
642
+ };
643
+ const rubricHash = sha256(canonical(rubric));
644
+ if (dryRun) {
645
+ return {
646
+ status: 'dry_run',
647
+ networkCalled: false,
648
+ projectionHash,
649
+ rubricHash,
650
+ eventCount: projection.trace.events.length,
651
+ projectionBytes: Buffer.byteLength(canonical(projection), 'utf8'),
652
+ };
653
+ }
654
+ // Check Keychain metadata only. The secret stays outside this
655
+ // content-addressing module and cannot enter a stored digest.
656
+ const sharedConfigured =
657
+ policy.enabled || env.GOFER_DISABLE_SHARED_CREDENTIALS === '1'
658
+ ? false
659
+ : await sharedCredentialConfigured({ keychain });
660
+ if (!policy.enabled && !sharedConfigured) {
661
+ return { status: 'disabled', networkCalled: false, projectionHash, rubricHash };
662
+ }
663
+ const providerResult = await requestTypeSafeEvaluation({
664
+ workspace: root,
665
+ env,
666
+ fetchImpl,
667
+ policy,
668
+ projection,
669
+ rubric,
670
+ keychain,
671
+ });
672
+ if (providerResult.status !== 'received') {
673
+ return { ...providerResult, projectionHash, rubricHash };
674
+ }
675
+ // Persist only a fixed label. Never carry provider credential data into
676
+ // content-addressed evaluation or candidate records.
677
+ const credentialSource = credentialSourceLabel(providerResult.credentialSource);
678
+ const { payload } = providerResult;
679
+ const answers = answerMap(payload);
680
+ const probabilities = {
681
+ taskSuccess: probability(answers.task_success),
682
+ reusableLesson: probability(answers.reusable_lesson),
683
+ evidenceSupport: probability(answers.evidence_support),
684
+ };
685
+ const passed =
686
+ probabilities.taskSuccess >= policy.thresholds.taskSuccess &&
687
+ probabilities.reusableLesson >= policy.thresholds.reusableLesson &&
688
+ probabilities.evidenceSupport >= policy.thresholds.evidenceSupport;
689
+ const evaluationSeed = {
690
+ schemaVersion: 1,
691
+ traceId: trace.traceId,
692
+ traceHash: trace.traceHash,
693
+ projectId: trace.projectId,
694
+ proposal: boundedProposal,
695
+ projectionHash,
696
+ evaluator: { provider: policy.provider, model: policy.model,
697
+ credentialSource },
698
+ rubricHash,
699
+ thresholds: policy.thresholds,
700
+ probabilities,
701
+ passed,
702
+ };
703
+ const evaluatedAt = new Date().toISOString();
704
+ const evaluationCore = {
705
+ ...evaluationSeed,
706
+ evaluatedAt,
707
+ };
708
+ const evaluation = {
709
+ evaluationId: `evaluation_${sha256(canonical(evaluationSeed)).slice(0, 32)}`,
710
+ ...evaluationCore,
711
+ };
712
+ const store = await ensureStore(root);
713
+ await appendPrivate(path.join(store, 'evaluations.jsonl'), evaluation);
714
+ if (!passed) return { status: 'not_candidate', networkCalled: true, evaluation };
715
+ const candidateSeed = {
716
+ schemaVersion: 1,
717
+ projectId: trace.projectId,
718
+ traceId: trace.traceId,
719
+ traceHash: trace.traceHash,
720
+ evaluationId: evaluation.evaluationId,
721
+ proposal: boundedProposal,
722
+ evaluator: evaluation.evaluator,
723
+ rubricHash,
724
+ probabilities,
725
+ };
726
+ const candidate = {
727
+ candidateId: `candidate_${sha256(canonical(candidateSeed)).slice(0, 32)}`,
728
+ ...candidateSeed,
729
+ createdAt: evaluatedAt,
730
+ state: 'candidate',
731
+ };
732
+ const existing = await currentCandidates(store, root);
733
+ if (!existing.has(candidate.candidateId)) {
734
+ await appendPrivate(path.join(store, 'candidates.jsonl'), { action: 'created', ...candidate });
735
+ }
736
+ return { status: 'candidate', networkCalled: true, evaluation, candidate };
737
+ }
738
+
739
+ function reviewTransactionPayload(transaction) {
740
+ const { approval, ...payload } = transaction;
741
+ return payload;
742
+ }
743
+
744
+ function signReviewTransaction(transaction, privateKey, keyId) {
745
+ const payload = reviewTransactionPayload(transaction);
746
+ return {
747
+ ...payload,
748
+ approval: {
749
+ algorithm: 'ed25519',
750
+ evaluator: VERIFIER_EVALUATOR,
751
+ keyId,
752
+ value: sign(null, Buffer.from(canonical(payload)), privateKey).toString('base64url'),
753
+ },
754
+ };
755
+ }
756
+
757
+ async function authenticReviewTransaction(transaction, workspace) {
758
+ const approval = transaction?.approval;
759
+ if (approval?.algorithm !== 'ed25519' || approval.evaluator !== VERIFIER_EVALUATOR ||
760
+ !text(approval.keyId) || !/^[A-Za-z0-9_-]+$/.test(approval.value ?? '')) return false;
761
+ try {
762
+ const publicKey = await resolveTrustedEvaluatorPublicKey({
763
+ host: 'codex',
764
+ provenance: { evaluator: approval.evaluator, keyId: approval.keyId },
765
+ }, { workspaceRoot: workspace });
766
+ return verify(null, Buffer.from(canonical(reviewTransactionPayload(transaction))), publicKey,
767
+ Buffer.from(approval.value, 'base64url'));
768
+ } catch {
769
+ return false;
770
+ }
771
+ }
772
+
773
+ async function trustedReviewTransactions(store, workspace) {
774
+ const transactions = await readJsonLines(path.join(store, 'review-transactions.jsonl'));
775
+ const trusted = [];
776
+ for (const transaction of transactions) {
777
+ const grantsMemory = (transaction.memories ?? []).some((record) =>
778
+ ['approved', 'superseded'].includes(record?.action));
779
+ if (!grantsMemory || await authenticReviewTransaction(transaction, workspace)) trusted.push(transaction);
780
+ }
781
+ return trusted;
782
+ }
783
+
784
+ async function currentCandidates(store, workspace) {
785
+ const records = await readJsonLines(path.join(store, 'candidates.jsonl'));
786
+ const transactions = await trustedReviewTransactions(store, workspace);
787
+ const state = new Map();
788
+ for (const record of records) {
789
+ if (record.action === 'created') state.set(record.candidateId, { ...record });
790
+ else if (state.has(record.candidateId)) {
791
+ state.set(record.candidateId, { ...state.get(record.candidateId), ...record });
792
+ }
793
+ }
794
+ for (const transaction of transactions) {
795
+ const record = transaction.candidate;
796
+ if (record && state.has(record.candidateId)) {
797
+ state.set(record.candidateId, { ...state.get(record.candidateId), ...record });
798
+ }
799
+ }
800
+ return state;
801
+ }
802
+
803
+ async function currentMemories(store, workspace) {
804
+ const records = await readJsonLines(path.join(store, 'memories.jsonl'));
805
+ const transactions = await trustedReviewTransactions(store, workspace);
806
+ const state = new Map();
807
+ for (const record of records) {
808
+ if (record.action === 'approved') state.set(record.memoryId, { ...record });
809
+ else if (state.has(record.memoryId)) state.set(record.memoryId, { ...state.get(record.memoryId), ...record });
810
+ }
811
+ for (const transaction of transactions) {
812
+ for (const record of transaction.memories ?? []) {
813
+ if (record.action === 'approved') state.set(record.memoryId, { ...record });
814
+ else if (state.has(record.memoryId)) state.set(record.memoryId, { ...state.get(record.memoryId), ...record });
815
+ }
816
+ }
817
+ return state;
818
+ }
819
+
820
+ async function recoverReviewLock(lockPath, expectedIdentity) {
821
+ const recoveryPath = `${lockPath}.recovery`;
822
+ let recovery;
823
+ try {
824
+ recovery = await fs.open(recoveryPath,
825
+ constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | noFollowFlag, 0o600);
826
+ } catch (error) {
827
+ if (error?.code === 'EEXIST') throw new Error('LEARNING_REVIEW_IN_PROGRESS');
828
+ throw error;
829
+ }
830
+ const recoveryIdentity = await recovery.stat();
831
+ try {
832
+ const current = await fs.lstat(lockPath).catch((error) => {
833
+ if (error?.code === 'ENOENT') return null;
834
+ throw error;
835
+ });
836
+ if (!current || current.isSymbolicLink() || !sameIdentity(expectedIdentity, current)) {
837
+ throw new Error('LEARNING_REVIEW_IN_PROGRESS');
838
+ }
839
+ await fs.unlink(lockPath);
840
+ } finally {
841
+ await recovery.close();
842
+ const currentRecovery = await fs.lstat(recoveryPath).catch((error) => {
843
+ if (error?.code === 'ENOENT') return null;
844
+ throw error;
845
+ });
846
+ if (currentRecovery && !currentRecovery.isSymbolicLink() &&
847
+ sameIdentity(recoveryIdentity, currentRecovery)) await fs.unlink(recoveryPath);
848
+ }
849
+ }
850
+
851
+ async function withReviewLock(store, action) {
852
+ const lockPath = path.join(store, 'review.lock');
853
+ return withVerifiedParent(lockPath, async () => {
854
+ let lock;
855
+ try {
856
+ lock = await fs.open(lockPath,
857
+ constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | noFollowFlag, 0o600);
858
+ } catch (error) {
859
+ if (error?.code !== 'EEXIST') throw error;
860
+ const existing = await fs.open(lockPath, constants.O_RDONLY | noFollowFlag);
861
+ let lockIdentity;
862
+ let record;
863
+ try {
864
+ lockIdentity = await existing.stat();
865
+ record = JSON.parse(await existing.readFile('utf8'));
866
+ } catch {
867
+ const current = await fs.lstat(lockPath);
868
+ const incompleteExpired = lockIdentity &&
869
+ Date.now() - lockIdentity.mtimeMs >= REVIEW_LOCK_TTL_MS &&
870
+ !current.isSymbolicLink() && sameIdentity(lockIdentity, current);
871
+ if (!incompleteExpired) throw new Error('LEARNING_REVIEW_IN_PROGRESS');
872
+ await recoverReviewLock(lockPath, lockIdentity);
873
+ return withReviewLock(store, action);
874
+ } finally {
875
+ await existing.close();
876
+ }
877
+ const expired = validTime(record?.expiresAt) && Date.parse(record.expiresAt) <= Date.now();
878
+ let ownerAlive = true;
879
+ if (Number.isSafeInteger(record?.pid) && record.pid > 0) {
880
+ try { process.kill(record.pid, 0); } catch (ownerError) {
881
+ ownerAlive = ownerError?.code !== 'ESRCH';
882
+ }
883
+ }
884
+ const current = await fs.lstat(lockPath);
885
+ if (!expired || ownerAlive || current.isSymbolicLink() || !sameIdentity(lockIdentity, current)) {
886
+ throw new Error('LEARNING_REVIEW_IN_PROGRESS');
887
+ }
888
+ await recoverReviewLock(lockPath, lockIdentity);
889
+ return withReviewLock(store, action);
890
+ }
891
+ const lockIdentity = await lock.stat();
892
+ const createdAt = new Date();
893
+ await lock.writeFile(JSON.stringify({
894
+ schemaVersion: 1,
895
+ pid: process.pid,
896
+ createdAt: createdAt.toISOString(),
897
+ expiresAt: new Date(createdAt.getTime() + REVIEW_LOCK_TTL_MS).toISOString(),
898
+ }));
899
+ await lock.sync();
900
+ try {
901
+ return await action();
902
+ } finally {
903
+ await lock.close();
904
+ const current = await fs.lstat(lockPath).catch((error) => {
905
+ if (error?.code === 'ENOENT') return null;
906
+ throw error;
907
+ });
908
+ if (current && !current.isSymbolicLink() && sameIdentity(lockIdentity, current)) {
909
+ await fs.unlink(lockPath);
910
+ }
911
+ }
912
+ });
913
+ }
914
+
915
+ export async function listCandidates({ workspace = process.cwd(), state } = {}) {
916
+ const root = await safeWorkspace(workspace);
917
+ const store = await ensureStore(root);
918
+ return [...(await currentCandidates(store, root)).values()]
919
+ .filter((candidate) => !state || candidate.state === state)
920
+ .sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt)));
921
+ }
922
+
923
+ export async function reviewCandidate({
924
+ workspace = process.cwd(),
925
+ candidateId,
926
+ decision,
927
+ actor,
928
+ reason,
929
+ replacementMemoryId,
930
+ now = () => new Date(),
931
+ }) {
932
+ const root = await safeWorkspace(workspace);
933
+ if (!/^candidate_[a-f0-9]{32}$/.test(candidateId ?? '') ||
934
+ !['approve', 'reject', 'supersede'].includes(decision) || !text(actor) || !text(reason)) {
935
+ throw new Error('LEARNING_REVIEW_INVALID');
936
+ }
937
+ let approvalAuthority;
938
+ if (['approve', 'supersede'].includes(decision)) {
939
+ try {
940
+ approvalAuthority = await loadActiveBenchmarkVerifierKey({
941
+ workspaceRoot: root,
942
+ getPassphrase: () => promptHidden(
943
+ `${decision === 'approve' ? 'Approve' : 'Supersede'} ${candidateId} — reviewer passphrase: `
944
+ ),
945
+ });
946
+ } catch (error) {
947
+ if (error?.message === 'TTY_REQUIRED' || error?.message === 'PASSPHRASE_REQUIRED') {
948
+ throw new Error('LEARNING_HUMAN_APPROVAL_REQUIRED');
949
+ }
950
+ throw error;
951
+ }
952
+ }
953
+ const store = await ensureStore(root);
954
+ return withReviewLock(store, async () => {
955
+ const candidates = await currentCandidates(store, root);
956
+ const candidate = candidates.get(candidateId);
957
+ if (!candidate) throw new Error('LEARNING_CANDIDATE_NOT_FOUND');
958
+ const reviewedAt = now().toISOString();
959
+ if (decision === 'approve') {
960
+ if (candidate.state !== 'candidate') throw new Error('LEARNING_CANDIDATE_ALREADY_REVIEWED');
961
+ const memoryCore = {
962
+ schemaVersion: 1,
963
+ candidateId,
964
+ projectId: candidate.projectId,
965
+ title: candidate.proposal.title,
966
+ content: candidate.proposal.lesson,
967
+ kind: candidate.proposal.kind,
968
+ traceId: candidate.traceId,
969
+ traceHash: candidate.traceHash,
970
+ evaluationId: candidate.evaluationId,
971
+ evaluator: candidate.evaluator,
972
+ rubricHash: candidate.rubricHash,
973
+ probabilities: candidate.probabilities,
974
+ approvedAt: reviewedAt,
975
+ approvedBy: redactString(actor, 200),
976
+ approvalReason: redactString(reason, 1000),
977
+ };
978
+ const memory = {
979
+ action: 'approved',
980
+ memoryId: `memory_${sha256(canonical(memoryCore)).slice(0, 32)}`,
981
+ ...memoryCore,
982
+ state: 'approved',
983
+ };
984
+ const candidateUpdate = {
985
+ action: 'reviewed',
986
+ candidateId,
987
+ state: 'approved',
988
+ memoryId: memory.memoryId,
989
+ reviewedAt,
990
+ reviewedBy: redactString(actor, 200),
991
+ reviewReason: redactString(reason, 1000),
992
+ };
993
+ const transaction = {
994
+ action: 'review_transaction',
995
+ transactionId: `review_${sha256(canonical({ candidateUpdate, memory })).slice(0, 32)}`,
996
+ candidate: candidateUpdate,
997
+ memories: [memory],
998
+ };
999
+ await appendPrivate(path.join(store, 'review-transactions.jsonl'),
1000
+ signReviewTransaction(transaction, approvalAuthority.privateKey, approvalAuthority.keyId));
1001
+ return { candidateId, state: 'approved', memory };
1002
+ }
1003
+ if (decision === 'reject') {
1004
+ if (candidate.state !== 'candidate') throw new Error('LEARNING_CANDIDATE_ALREADY_REVIEWED');
1005
+ const review = {
1006
+ action: 'reviewed',
1007
+ candidateId,
1008
+ state: 'rejected',
1009
+ reviewedAt,
1010
+ reviewedBy: redactString(actor, 200),
1011
+ reviewReason: redactString(reason, 1000),
1012
+ };
1013
+ await appendPrivate(path.join(store, 'review-transactions.jsonl'), {
1014
+ action: 'review_transaction',
1015
+ transactionId: `review_${sha256(canonical(review)).slice(0, 32)}`,
1016
+ candidate: review,
1017
+ memories: [],
1018
+ });
1019
+ return review;
1020
+ }
1021
+ if (candidate.state !== 'approved' || candidate.memoryId === replacementMemoryId ||
1022
+ !/^memory_[a-f0-9]{32}$/.test(replacementMemoryId ?? '')) {
1023
+ throw new Error('LEARNING_SUPERSEDE_INVALID');
1024
+ }
1025
+ const memories = await currentMemories(store, root);
1026
+ const current = memories.get(candidate.memoryId);
1027
+ const replacement = memories.get(replacementMemoryId);
1028
+ if (!current || !replacement || current.projectId !== replacement.projectId ||
1029
+ current.projectId !== candidate.projectId || replacement.state !== 'approved') {
1030
+ throw new Error('LEARNING_REPLACEMENT_INVALID');
1031
+ }
1032
+ const update = {
1033
+ action: 'superseded',
1034
+ memoryId: current.memoryId,
1035
+ state: 'superseded',
1036
+ supersededBy: replacementMemoryId,
1037
+ reviewedAt,
1038
+ reviewedBy: redactString(actor, 200),
1039
+ reviewReason: redactString(reason, 1000),
1040
+ };
1041
+ const candidateUpdate = {
1042
+ action: 'reviewed',
1043
+ candidateId,
1044
+ state: 'superseded',
1045
+ replacementMemoryId,
1046
+ reviewedAt,
1047
+ reviewedBy: update.reviewedBy,
1048
+ reviewReason: update.reviewReason,
1049
+ };
1050
+ const transaction = {
1051
+ action: 'review_transaction',
1052
+ transactionId: `review_${sha256(canonical({ candidateUpdate, update })).slice(0, 32)}`,
1053
+ candidate: candidateUpdate,
1054
+ memories: [update],
1055
+ };
1056
+ await appendPrivate(path.join(store, 'review-transactions.jsonl'),
1057
+ signReviewTransaction(transaction, approvalAuthority.privateKey, approvalAuthority.keyId));
1058
+ return update;
1059
+ });
1060
+ }
1061
+
1062
+ function queryTerms(query) {
1063
+ return [...new Set(String(query).toLowerCase().match(/[a-z0-9_-]{3,}/g) ?? [])].slice(0, 100);
1064
+ }
1065
+
1066
+ export async function searchApprovedMemory({ workspace = process.cwd(), query, limit = 10 } = {}) {
1067
+ if (!text(query) || !Number.isSafeInteger(limit) || limit < 1 || limit > 100) {
1068
+ throw new Error('LEARNING_SEARCH_INVALID');
1069
+ }
1070
+ const root = await safeWorkspace(workspace);
1071
+ const store = await ensureStore(root);
1072
+ const projectId = `project_${sha256(root).slice(0, 24)}`;
1073
+ const terms = queryTerms(query);
1074
+ return [...(await currentMemories(store, root)).values()]
1075
+ .filter((memory) => memory.projectId === projectId && memory.state === 'approved')
1076
+ .map((memory) => {
1077
+ const haystack = `${memory.title} ${memory.content} ${memory.kind}`.toLowerCase();
1078
+ const matches = terms.filter((term) => haystack.includes(term)).length;
1079
+ return { ...memory, relevance: terms.length ? matches / terms.length : 0 };
1080
+ })
1081
+ .filter((memory) => memory.relevance > 0)
1082
+ .sort((a, b) => b.relevance - a.relevance || String(b.approvedAt).localeCompare(String(a.approvedAt)))
1083
+ .slice(0, limit);
1084
+ }
1085
+
1086
+ export async function recordMemoryOutcome({
1087
+ workspace = process.cwd(),
1088
+ memoryId,
1089
+ runId,
1090
+ outcome,
1091
+ note = '',
1092
+ now = () => new Date(),
1093
+ }) {
1094
+ if (!/^memory_[a-f0-9]{32}$/.test(memoryId ?? '') || !text(runId) ||
1095
+ !['helped', 'neutral', 'harmed'].includes(outcome)) {
1096
+ throw new Error('LEARNING_FEEDBACK_INVALID');
1097
+ }
1098
+ const root = await safeWorkspace(workspace);
1099
+ const store = await ensureStore(root);
1100
+ return withReviewLock(store, async () => {
1101
+ const memory = (await currentMemories(store, root)).get(memoryId);
1102
+ if (!memory || memory.state !== 'approved') throw new Error('LEARNING_MEMORY_NOT_APPROVED');
1103
+ const feedbackCore = {
1104
+ schemaVersion: 1,
1105
+ memoryId,
1106
+ projectId: memory.projectId,
1107
+ runId: truncateUtf8(runId, 200),
1108
+ outcome,
1109
+ note: redactString(note, 1000),
1110
+ };
1111
+ const feedback = {
1112
+ ...feedbackCore,
1113
+ feedbackId: `feedback_${sha256(canonical(feedbackCore)).slice(0, 32)}`,
1114
+ recordedAt: now().toISOString(),
1115
+ };
1116
+ const existing = await readJsonLines(path.join(store, 'feedback.jsonl'));
1117
+ if (existing.some((item) => item.feedbackId === feedback.feedbackId)) return feedback;
1118
+ await appendPrivate(path.join(store, 'feedback.jsonl'), feedback);
1119
+ return feedback;
1120
+ });
1121
+ }
1122
+
1123
+ export async function learningReport({ workspace = process.cwd() } = {}) {
1124
+ const root = await safeWorkspace(workspace);
1125
+ const store = await ensureStore(root);
1126
+ const projectId = `project_${sha256(root).slice(0, 24)}`;
1127
+ const candidates = [...(await currentCandidates(store, root)).values()].filter((item) => item.projectId === projectId);
1128
+ const memories = [...(await currentMemories(store, root)).values()].filter((item) => item.projectId === projectId);
1129
+ const feedback = (await readJsonLines(path.join(store, 'feedback.jsonl'))).filter((item) => item.projectId === projectId);
1130
+ const outcomes = Object.fromEntries(['helped', 'neutral', 'harmed'].map((name) => [
1131
+ name,
1132
+ feedback.filter((item) => item.outcome === name).length,
1133
+ ]));
1134
+ return {
1135
+ projectId,
1136
+ candidates: {
1137
+ total: candidates.length,
1138
+ pending: candidates.filter((item) => item.state === 'candidate').length,
1139
+ approved: candidates.filter((item) => item.state === 'approved').length,
1140
+ rejected: candidates.filter((item) => item.state === 'rejected').length,
1141
+ superseded: candidates.filter((item) => item.state === 'superseded').length,
1142
+ },
1143
+ memories: {
1144
+ approved: memories.filter((item) => item.state === 'approved').length,
1145
+ superseded: memories.filter((item) => item.state === 'superseded').length,
1146
+ },
1147
+ feedback: { total: feedback.length, ...outcomes },
1148
+ };
1149
+ }
1150
+
1151
+ function parseArgs(argv) {
1152
+ const [action, ...rest] = argv;
1153
+ const flags = new Map();
1154
+ for (let index = 0; index < rest.length; index += 1) {
1155
+ const name = rest[index];
1156
+ if (!name.startsWith('--')) throw new Error('LEARNING_CLI_INVALID');
1157
+ if (name === '--dry-run') flags.set(name, true);
1158
+ else {
1159
+ const value = rest[++index];
1160
+ if (!value || value.startsWith('--')) throw new Error(`Missing value for ${name}`);
1161
+ flags.set(name, value);
1162
+ }
1163
+ }
1164
+ return { action, flags };
1165
+ }
1166
+
1167
+ async function main() {
1168
+ const { action, flags } = parseArgs(process.argv.slice(2));
1169
+ const workspace = flags.get('--workspace') ?? process.cwd();
1170
+ let result;
1171
+ if (action === 'normalize') {
1172
+ result = await normalizeJournal({
1173
+ workspace,
1174
+ input: flags.get('--input'),
1175
+ host: flags.get('--host'),
1176
+ objective: flags.get('--objective'),
1177
+ sessionId: flags.get('--session'),
1178
+ sourceKind: flags.get('--source-kind') ?? 'verified-execution-journal',
1179
+ });
1180
+ result = { traceId: result.trace.traceId, tracePath: result.tracePath };
1181
+ } else if (action === 'evaluate') {
1182
+ const traceFile = await readBoundedFile(workspace, flags.get('--trace'));
1183
+ const lessonFile = await readBoundedFile(workspace, flags.get('--lesson-file'), MAX_LESSON_BYTES);
1184
+ result = await evaluateTrace({
1185
+ workspace,
1186
+ trace: JSON.parse(traceFile.content),
1187
+ proposal: {
1188
+ title: flags.get('--title'),
1189
+ lesson: lessonFile.content,
1190
+ kind: flags.get('--kind'),
1191
+ },
1192
+ dryRun: flags.get('--dry-run') === true,
1193
+ });
1194
+ } else if (action === 'candidates') {
1195
+ result = await listCandidates({ workspace, state: flags.get('--state') });
1196
+ } else if (action === 'review') {
1197
+ const decision = flags.get('--decision');
1198
+ result = await reviewCandidate({
1199
+ workspace,
1200
+ candidateId: flags.get('--candidate'),
1201
+ decision,
1202
+ actor: flags.get('--actor'),
1203
+ reason: flags.get('--reason'),
1204
+ replacementMemoryId: flags.get('--replacement'),
1205
+ });
1206
+ } else if (action === 'search') {
1207
+ result = await searchApprovedMemory({
1208
+ workspace,
1209
+ query: flags.get('--query'),
1210
+ limit: flags.has('--limit') ? Number(flags.get('--limit')) : 10,
1211
+ });
1212
+ } else if (action === 'feedback') {
1213
+ result = await recordMemoryOutcome({
1214
+ workspace,
1215
+ memoryId: flags.get('--memory'),
1216
+ runId: flags.get('--run'),
1217
+ outcome: flags.get('--outcome'),
1218
+ note: flags.get('--note'),
1219
+ });
1220
+ } else if (action === 'report') {
1221
+ result = await learningReport({ workspace });
1222
+ } else {
1223
+ throw new Error('Usage: normalize | evaluate | candidates | review | search | feedback | report');
1224
+ }
1225
+ process.stdout.write(`${JSON.stringify(result)}\n`);
1226
+ }
1227
+
1228
+ const direct = process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href;
1229
+ if (direct) main().catch((error) => {
1230
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
1231
+ process.exitCode = 1;
1232
+ });