@enterpriseai/cli 3.19.0 → 3.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (362) hide show
  1. package/package.json +1 -1
  2. package/resources/gofer/.gofer-version +4 -357
  3. package/resources/gofer/agents-skills/eai/SKILL.md +365 -0
  4. package/resources/gofer/agents-skills/eai-update/SKILL.md +56 -0
  5. package/resources/gofer/agents-skills/gofer-documentation/SKILL.md +93 -0
  6. package/resources/gofer/bash-scripts/create-new-feature.sh +3 -1
  7. package/resources/gofer/bash-scripts/sync-implementation-status.sh +4 -3
  8. package/resources/gofer/bash-scripts/update-agent-context.sh +7 -8
  9. package/resources/gofer/bash-scripts/validate-artifact.sh +10 -6
  10. package/resources/gofer/bash-scripts/verify-object-type-routing-workspace.sh +184 -0
  11. package/resources/gofer/claude-commands/eai-update.md +46 -0
  12. package/resources/gofer/claude-commands/eai.md +355 -0
  13. package/resources/gofer/claude-skills/eai/SKILL.md +365 -0
  14. package/resources/gofer/claude-skills/eai-update/SKILL.md +56 -0
  15. package/resources/gofer/claude-skills/gofer-documentation/SKILL.md +93 -0
  16. package/resources/gofer/commands/0_gofer_start.md +440 -99
  17. package/resources/gofer/commands/0a_problem_validation.md +187 -27
  18. package/resources/gofer/commands/10_gofer_cloud.md +183 -23
  19. package/resources/gofer/commands/1_gofer_research.md +239 -30
  20. package/resources/gofer/commands/2_gofer_specify.md +239 -35
  21. package/resources/gofer/commands/3_gofer_plan.md +259 -43
  22. package/resources/gofer/commands/4_gofer_tasks.md +401 -81
  23. package/resources/gofer/commands/5_gofer_implement.md +375 -84
  24. package/resources/gofer/commands/6_gofer_validate.md +284 -53
  25. package/resources/gofer/commands/7_gofer_save.md +187 -26
  26. package/resources/gofer/commands/7a_stakeholder_comms.md +183 -23
  27. package/resources/gofer/commands/8_gofer_branding.md +183 -23
  28. package/resources/gofer/commands/9_gofer_tests.md +212 -23
  29. package/resources/gofer/commands/gofer_bootstrap_workspace.md +121 -8
  30. package/resources/gofer/commands/gofer_check_workspace.md +123 -8
  31. package/resources/gofer/commands/gofer_constitution.md +183 -23
  32. package/resources/gofer/commands/gofer_diagnose.md +183 -23
  33. package/resources/gofer/commands/gofer_eai_first_run.md +254 -25
  34. package/resources/gofer/commands/gofer_hydrate.md +183 -23
  35. package/resources/gofer/commands/gofer_personality.md +117 -6
  36. package/resources/gofer/commands/gofer_plan.md +117 -6
  37. package/resources/gofer/commands/gofer_side.md +117 -6
  38. package/resources/gofer/commands/gofer_spec_summary.md +183 -23
  39. package/resources/gofer/commands/gofer_tdd.md +183 -23
  40. package/resources/gofer/commands/gofer_vocabulary.md +185 -25
  41. package/resources/gofer/commands/gofer_zoom_out.md +183 -23
  42. package/resources/gofer/config/typesafe-learning-review.json +16 -0
  43. package/resources/gofer/config/typesafe-semantic-review.json +9 -0
  44. package/resources/gofer/copilot-instructions/gofer-cost.instructions.md +15 -6
  45. package/resources/gofer/copilot-instructions/typescript.instructions.md +1 -1
  46. package/resources/gofer/copilot-prompts/eai-update.prompt.md +58 -0
  47. package/resources/gofer/copilot-prompts/eai.prompt.md +367 -0
  48. package/resources/gofer/gemini/commands/gofer/eai-update.md +22 -0
  49. package/resources/gofer/gemini/commands/gofer/eai-update.toml +2 -0
  50. package/resources/gofer/gemini/commands/gofer/eai.md +357 -0
  51. package/resources/gofer/gemini/commands/gofer/eai.toml +2 -0
  52. package/resources/gofer/gemini/commands/gofer/manifest.json +3 -27
  53. package/resources/gofer/gemini/extension.json +4 -4
  54. package/resources/gofer/github-agents/eai.agent.md +83 -0
  55. package/resources/gofer/github-agents/gofer-business.agent.md +73 -4
  56. package/resources/gofer/github-agents/gofer-implement.agent.md +71 -2
  57. package/resources/gofer/github-agents/gofer-plan.agent.md +72 -2
  58. package/resources/gofer/github-agents/gofer-research.agent.md +72 -2
  59. package/resources/gofer/github-agents/gofer-validate.agent.md +72 -2
  60. package/resources/gofer/github-skills/eai/SKILL.md +365 -0
  61. package/resources/gofer/github-skills/eai-update/SKILL.md +56 -0
  62. package/resources/gofer/github-skills/gofer-documentation/SKILL.md +93 -0
  63. package/resources/gofer/grok-skills/eai/SKILL.md +267 -14
  64. package/resources/gofer/grok-skills/eai-update/SKILL.md +56 -0
  65. package/resources/gofer/hook-scripts/user-prompt-submit.mjs +58 -13
  66. package/resources/gofer/instruction-templates/base/agents-base.md +8 -4
  67. package/resources/gofer/instruction-templates/gofer/gofer-claude.md +7 -5
  68. package/resources/gofer/instruction-templates/gofer/gofer-copilot.md +10 -8
  69. package/resources/gofer/memory/constitution.md +9 -7
  70. package/resources/gofer/memory/gofer-model-policy.yaml +23 -1
  71. package/resources/gofer/memory/lessons.md +0 -31
  72. package/resources/gofer/node-scripts/canonical-descriptions.mjs +1 -1
  73. package/resources/gofer/node-scripts/check-version-alignment.mjs +4 -7
  74. package/resources/gofer/node-scripts/delivery-lineage-corpus.mjs +156 -0
  75. package/resources/gofer/node-scripts/generate-commands.mjs +1073 -265
  76. package/resources/gofer/node-scripts/gofer-acceptance-check.mjs +143 -0
  77. package/resources/gofer/node-scripts/gofer-agent-catalog.mjs +271 -0
  78. package/resources/gofer/node-scripts/gofer-benchmark-executor.mjs +123 -0
  79. package/resources/gofer/node-scripts/gofer-benchmark-sign.mjs +62 -0
  80. package/resources/gofer/node-scripts/gofer-benchmark-signer.mjs +89 -0
  81. package/resources/gofer/node-scripts/gofer-benchmark.mjs +76 -0
  82. package/resources/gofer/node-scripts/gofer-blocker-control.mjs +274 -0
  83. package/resources/gofer/node-scripts/gofer-claude-adapter.mjs +154 -0
  84. package/resources/gofer/node-scripts/gofer-closed-loop-audit.mjs +19 -2
  85. package/resources/gofer/node-scripts/gofer-delivery-check.mjs +121 -0
  86. package/resources/gofer/node-scripts/gofer-encrypted-key.mjs +58 -0
  87. package/resources/gofer/node-scripts/gofer-execution-metrics.mjs +477 -0
  88. package/resources/gofer/node-scripts/gofer-execution-recovery.mjs +334 -0
  89. package/resources/gofer/node-scripts/gofer-grok-adapter.mjs +184 -0
  90. package/resources/gofer/node-scripts/gofer-heldout-corpus.mjs +73 -0
  91. package/resources/gofer/node-scripts/gofer-heldout-snapshot.mjs +286 -0
  92. package/resources/gofer/node-scripts/gofer-heldout-verifier.mjs +211 -0
  93. package/resources/gofer/node-scripts/gofer-host-capability.mjs +335 -0
  94. package/resources/gofer/node-scripts/gofer-independent-reviewer.mjs +130 -0
  95. package/resources/gofer/node-scripts/gofer-live-routing.mjs +109 -0
  96. package/resources/gofer/node-scripts/gofer-local-capability-issuer.mjs +71 -0
  97. package/resources/gofer/node-scripts/gofer-local-isolation.mjs +175 -0
  98. package/resources/gofer/node-scripts/gofer-local-settings-cleanup.mjs +388 -0
  99. package/resources/gofer/node-scripts/gofer-loop-audit.mjs +54 -3
  100. package/resources/gofer/node-scripts/gofer-native-adapter.mjs +567 -0
  101. package/resources/gofer/node-scripts/gofer-native-benchmark-dispatch.mjs +145 -0
  102. package/resources/gofer/node-scripts/gofer-native-runtime.mjs +169 -0
  103. package/resources/gofer/node-scripts/gofer-pre-edit-check.mjs +204 -0
  104. package/resources/gofer/node-scripts/gofer-priority-check.mjs +214 -0
  105. package/resources/gofer/node-scripts/gofer-response-check.mjs +56 -0
  106. package/resources/gofer/node-scripts/gofer-reviewed-learning.mjs +1232 -0
  107. package/resources/gofer/node-scripts/gofer-run-verified-task.mjs +330 -0
  108. package/resources/gofer/node-scripts/gofer-runtime-ledger.mjs +289 -0
  109. package/resources/gofer/node-scripts/gofer-semantic-drift.mjs +584 -0
  110. package/resources/gofer/node-scripts/gofer-surface-update.mjs +1178 -0
  111. package/resources/gofer/node-scripts/gofer-trust-bootstrap.mjs +77 -0
  112. package/resources/gofer/node-scripts/gofer-trusted-benchmark.mjs +102 -0
  113. package/resources/gofer/node-scripts/gofer-trusted-evaluator.mjs +274 -0
  114. package/resources/gofer/node-scripts/gofer-tty-prompt.mjs +34 -0
  115. package/resources/gofer/node-scripts/gofer-typesafe-credentials.mjs +435 -0
  116. package/resources/gofer/node-scripts/gofer-ui-preview.mjs +1212 -0
  117. package/resources/gofer/node-scripts/gofer-verified-execution.mjs +484 -0
  118. package/resources/gofer/node-scripts/gofer-verifier-key-ceremony.mjs +101 -0
  119. package/resources/gofer/node-scripts/gofer-worktree-layout.mjs +32 -0
  120. package/resources/gofer/node-scripts/object-type-routing-phase-bundle.mjs +360 -0
  121. package/resources/gofer/node-scripts/package-agent-plugin.mjs +574 -82
  122. package/resources/gofer/node-scripts/parse-stage-command.mjs +1 -1
  123. package/resources/gofer/node-scripts/render-delivery-lineage.mjs +178 -0
  124. package/resources/gofer/node-scripts/sync-extension-resources.mjs +78 -2
  125. package/resources/gofer/node-scripts/validate-object-type-identifiers.mjs +767 -0
  126. package/resources/gofer/node-scripts/validate-object-type-routing-workspace.mjs +1325 -0
  127. package/resources/gofer/node-scripts/validate-v4-resource-contract.d.mts +44 -0
  128. package/resources/gofer/node-scripts/validate-v4-resource-contract.mjs +957 -0
  129. package/resources/gofer/node-scripts/workspace-bootstrap-lib.mjs +685 -116
  130. package/resources/gofer/references/agent-catalog.json +126 -0
  131. package/resources/gofer/references/blocker-mediation.md +156 -0
  132. package/resources/gofer/references/business-updates-and-goal-checks.md +109 -0
  133. package/resources/gofer/references/delivery-lineage.md +48 -0
  134. package/resources/gofer/references/mvp-capability-validation.md +102 -0
  135. package/resources/gofer/references/platform/README.md +1 -1
  136. package/resources/gofer/references/platform/eai-app-template.md +15 -13
  137. package/resources/gofer/references/platform/eai-auth-access.md +104 -0
  138. package/resources/gofer/references/platform/eai-config-driven-ui.md +5 -5
  139. package/resources/gofer/references/platform/eai-error-catalog.yaml +77 -19
  140. package/resources/gofer/references/platform/eai-repo-contract.md +61 -23
  141. package/resources/gofer/references/platform/eai-service-patterns.md +102 -105
  142. package/resources/gofer/references/platform/eai.md +44 -8
  143. package/resources/gofer/references/platform/vertical-template.md +10 -9
  144. package/resources/gofer/references/priority-outcome-protection.md +152 -0
  145. package/resources/gofer/references/verified-agent-execution.md +179 -0
  146. package/resources/gofer/schemas/gofer-learning-trace-v1.json +70 -0
  147. package/resources/gofer/specify-commands/0_gofer_start.md +440 -99
  148. package/resources/gofer/specify-commands/0a_problem_validation.md +187 -27
  149. package/resources/gofer/specify-commands/10_gofer_cloud.md +183 -23
  150. package/resources/gofer/specify-commands/1_gofer_research.md +239 -30
  151. package/resources/gofer/specify-commands/2_gofer_specify.md +239 -35
  152. package/resources/gofer/specify-commands/3_gofer_plan.md +259 -43
  153. package/resources/gofer/specify-commands/4_gofer_tasks.md +401 -81
  154. package/resources/gofer/specify-commands/5_gofer_implement.md +375 -84
  155. package/resources/gofer/specify-commands/6_gofer_validate.md +284 -53
  156. package/resources/gofer/specify-commands/7_gofer_save.md +187 -26
  157. package/resources/gofer/specify-commands/7a_stakeholder_comms.md +183 -23
  158. package/resources/gofer/specify-commands/8_gofer_branding.md +183 -23
  159. package/resources/gofer/specify-commands/9_gofer_tests.md +212 -23
  160. package/resources/gofer/specify-commands/gofer_bootstrap_workspace.md +121 -8
  161. package/resources/gofer/specify-commands/gofer_check_workspace.md +123 -8
  162. package/resources/gofer/specify-commands/gofer_constitution.md +183 -23
  163. package/resources/gofer/specify-commands/gofer_diagnose.md +183 -23
  164. package/resources/gofer/specify-commands/gofer_eai_first_run.md +254 -25
  165. package/resources/gofer/specify-commands/gofer_hydrate.md +183 -23
  166. package/resources/gofer/specify-commands/gofer_personality.md +117 -6
  167. package/resources/gofer/specify-commands/gofer_plan.md +117 -6
  168. package/resources/gofer/specify-commands/gofer_side.md +117 -6
  169. package/resources/gofer/specify-commands/gofer_spec_summary.md +183 -23
  170. package/resources/gofer/specify-commands/gofer_tdd.md +183 -23
  171. package/resources/gofer/specify-commands/gofer_vocabulary.md +185 -25
  172. package/resources/gofer/specify-commands/gofer_zoom_out.md +183 -23
  173. package/resources/gofer/specify-config/object-type-routing.json +24 -0
  174. package/resources/gofer/specify-config/typesafe-learning-review.json +16 -0
  175. package/resources/gofer/specify-config/typesafe-semantic-review.json +9 -0
  176. package/resources/gofer/system-skills/eai/SKILL.md +365 -0
  177. package/resources/gofer/system-skills/eai-update/SKILL.md +56 -0
  178. package/resources/gofer/system-skills/gofer-documentation/SKILL.md +93 -0
  179. package/resources/gofer/templates/build-map-template.md +64 -0
  180. package/resources/gofer/templates/business-scenarios-template.json +14 -0
  181. package/resources/gofer/templates/ciso-security-summary-template.md +3 -3
  182. package/resources/gofer/templates/context-bundle-template.md +4 -3
  183. package/resources/gofer/templates/contract-pack-template.md +8 -8
  184. package/resources/gofer/templates/cto-architecture-summary-template.md +3 -3
  185. package/resources/gofer/templates/delivery-lineage-template.json +8 -0
  186. package/resources/gofer/templates/eai-preflight-template.md +109 -68
  187. package/resources/gofer/templates/goal-ledger-template.json +1 -1
  188. package/resources/gofer/templates/gofer-model-policy.yaml +28 -6
  189. package/resources/gofer/templates/loop-contract-template.json +3 -1
  190. package/resources/gofer/templates/plan-template.md +8 -7
  191. package/resources/gofer/templates/priority-plan-template.json +29 -0
  192. package/resources/gofer/templates/proposal-review-template.md +6 -3
  193. package/resources/gofer/templates/release-capability-ledger-template.md +24 -0
  194. package/resources/gofer/templates/research-template.md +7 -6
  195. package/resources/gofer/templates/spec-summary-template.md +1 -1
  196. package/resources/gofer/templates/spec-template.md +45 -6
  197. package/resources/gofer/templates/tasks-template.md +12 -3
  198. package/resources/gofer/templates/ui-preview-brief-template.md +55 -5
  199. package/resources/gofer/templates/ui-review-log-template.md +21 -3
  200. package/resources/gofer/templates/ui-show-and-tell-template.md +46 -0
  201. package/resources/gofer/templates/visuals/capability-heatmap-template.md +2 -2
  202. package/resources/gofer/templates/working-backwards-prfaq-template.md +9 -8
  203. package/resources/linked-sources.json +2 -2
  204. package/resources/gofer/agents-skills/0_gofer_start/SKILL.md +0 -1287
  205. package/resources/gofer/agents-skills/0a_problem_validation/SKILL.md +0 -429
  206. package/resources/gofer/agents-skills/10_gofer_cloud/SKILL.md +0 -635
  207. package/resources/gofer/agents-skills/1_gofer_research/SKILL.md +0 -1159
  208. package/resources/gofer/agents-skills/2_gofer_specify/SKILL.md +0 -860
  209. package/resources/gofer/agents-skills/3_gofer_plan/SKILL.md +0 -899
  210. package/resources/gofer/agents-skills/4_gofer_tasks/SKILL.md +0 -760
  211. package/resources/gofer/agents-skills/5_gofer_implement/SKILL.md +0 -944
  212. package/resources/gofer/agents-skills/6_gofer_validate/SKILL.md +0 -2302
  213. package/resources/gofer/agents-skills/7_gofer_save/SKILL.md +0 -423
  214. package/resources/gofer/agents-skills/7a_stakeholder_comms/SKILL.md +0 -508
  215. package/resources/gofer/agents-skills/8_gofer_branding/SKILL.md +0 -315
  216. package/resources/gofer/agents-skills/9_gofer_tests/SKILL.md +0 -558
  217. package/resources/gofer/agents-skills/gofer_bootstrap_workspace/SKILL.md +0 -148
  218. package/resources/gofer/agents-skills/gofer_check_workspace/SKILL.md +0 -119
  219. package/resources/gofer/agents-skills/gofer_constitution/SKILL.md +0 -612
  220. package/resources/gofer/agents-skills/gofer_diagnose/SKILL.md +0 -114
  221. package/resources/gofer/agents-skills/gofer_eai_first_run/SKILL.md +0 -397
  222. package/resources/gofer/agents-skills/gofer_hydrate/SKILL.md +0 -561
  223. package/resources/gofer/agents-skills/gofer_personality/SKILL.md +0 -44
  224. package/resources/gofer/agents-skills/gofer_plan/SKILL.md +0 -40
  225. package/resources/gofer/agents-skills/gofer_side/SKILL.md +0 -45
  226. package/resources/gofer/agents-skills/gofer_spec_summary/SKILL.md +0 -112
  227. package/resources/gofer/agents-skills/gofer_tdd/SKILL.md +0 -112
  228. package/resources/gofer/agents-skills/gofer_vocabulary/SKILL.md +0 -112
  229. package/resources/gofer/agents-skills/gofer_zoom_out/SKILL.md +0 -112
  230. package/resources/gofer/claude-commands/0_gofer_start.md +0 -1282
  231. package/resources/gofer/claude-commands/0a_problem_validation.md +0 -424
  232. package/resources/gofer/claude-commands/10_gofer_cloud.md +0 -630
  233. package/resources/gofer/claude-commands/1_gofer_research.md +0 -1154
  234. package/resources/gofer/claude-commands/2_gofer_specify.md +0 -855
  235. package/resources/gofer/claude-commands/3_gofer_plan.md +0 -894
  236. package/resources/gofer/claude-commands/4_gofer_tasks.md +0 -755
  237. package/resources/gofer/claude-commands/5_gofer_implement.md +0 -939
  238. package/resources/gofer/claude-commands/6_gofer_validate.md +0 -2297
  239. package/resources/gofer/claude-commands/7_gofer_save.md +0 -418
  240. package/resources/gofer/claude-commands/7a_stakeholder_comms.md +0 -503
  241. package/resources/gofer/claude-commands/8_gofer_branding.md +0 -310
  242. package/resources/gofer/claude-commands/9_gofer_tests.md +0 -553
  243. package/resources/gofer/claude-commands/gofer_bootstrap_workspace.md +0 -143
  244. package/resources/gofer/claude-commands/gofer_check_workspace.md +0 -114
  245. package/resources/gofer/claude-commands/gofer_constitution.md +0 -607
  246. package/resources/gofer/claude-commands/gofer_diagnose.md +0 -109
  247. package/resources/gofer/claude-commands/gofer_eai_first_run.md +0 -392
  248. package/resources/gofer/claude-commands/gofer_hydrate.md +0 -556
  249. package/resources/gofer/claude-commands/gofer_personality.md +0 -39
  250. package/resources/gofer/claude-commands/gofer_plan.md +0 -35
  251. package/resources/gofer/claude-commands/gofer_side.md +0 -40
  252. package/resources/gofer/claude-commands/gofer_spec_summary.md +0 -107
  253. package/resources/gofer/claude-commands/gofer_tdd.md +0 -107
  254. package/resources/gofer/claude-commands/gofer_vocabulary.md +0 -107
  255. package/resources/gofer/claude-commands/gofer_zoom_out.md +0 -107
  256. package/resources/gofer/claude-skills/eai-gofer/SKILL.md +0 -56
  257. package/resources/gofer/copilot-prompts/0_gofer_start.prompt.md +0 -1308
  258. package/resources/gofer/copilot-prompts/0a_problem_validation.prompt.md +0 -429
  259. package/resources/gofer/copilot-prompts/10_gofer_cloud.prompt.md +0 -644
  260. package/resources/gofer/copilot-prompts/1_gofer_research.prompt.md +0 -1177
  261. package/resources/gofer/copilot-prompts/2_gofer_specify.prompt.md +0 -878
  262. package/resources/gofer/copilot-prompts/3_gofer_plan.prompt.md +0 -917
  263. package/resources/gofer/copilot-prompts/4_gofer_tasks.prompt.md +0 -778
  264. package/resources/gofer/copilot-prompts/5_gofer_implement.prompt.md +0 -962
  265. package/resources/gofer/copilot-prompts/6_gofer_validate.prompt.md +0 -2310
  266. package/resources/gofer/copilot-prompts/7_gofer_save.prompt.md +0 -433
  267. package/resources/gofer/copilot-prompts/7a_stakeholder_comms.prompt.md +0 -516
  268. package/resources/gofer/copilot-prompts/8_gofer_branding.prompt.md +0 -323
  269. package/resources/gofer/copilot-prompts/9_gofer_tests.prompt.md +0 -566
  270. package/resources/gofer/copilot-prompts/gofer_bootstrap_workspace.prompt.md +0 -161
  271. package/resources/gofer/copilot-prompts/gofer_check_workspace.prompt.md +0 -132
  272. package/resources/gofer/copilot-prompts/gofer_constitution.prompt.md +0 -621
  273. package/resources/gofer/copilot-prompts/gofer_diagnose.prompt.md +0 -127
  274. package/resources/gofer/copilot-prompts/gofer_eai_first_run.prompt.md +0 -410
  275. package/resources/gofer/copilot-prompts/gofer_hydrate.prompt.md +0 -571
  276. package/resources/gofer/copilot-prompts/gofer_personality.prompt.md +0 -57
  277. package/resources/gofer/copilot-prompts/gofer_plan.prompt.md +0 -53
  278. package/resources/gofer/copilot-prompts/gofer_side.prompt.md +0 -58
  279. package/resources/gofer/copilot-prompts/gofer_spec_summary.prompt.md +0 -125
  280. package/resources/gofer/copilot-prompts/gofer_tdd.prompt.md +0 -125
  281. package/resources/gofer/copilot-prompts/gofer_vocabulary.prompt.md +0 -125
  282. package/resources/gofer/copilot-prompts/gofer_zoom_out.prompt.md +0 -125
  283. package/resources/gofer/gemini/commands/gofer/0_gofer_start.md +0 -1282
  284. package/resources/gofer/gemini/commands/gofer/0_gofer_start.toml +0 -2
  285. package/resources/gofer/gemini/commands/gofer/0a_problem_validation.md +0 -424
  286. package/resources/gofer/gemini/commands/gofer/0a_problem_validation.toml +0 -2
  287. package/resources/gofer/gemini/commands/gofer/10_gofer_cloud.md +0 -630
  288. package/resources/gofer/gemini/commands/gofer/10_gofer_cloud.toml +0 -2
  289. package/resources/gofer/gemini/commands/gofer/1_gofer_research.md +0 -1154
  290. package/resources/gofer/gemini/commands/gofer/1_gofer_research.toml +0 -2
  291. package/resources/gofer/gemini/commands/gofer/2_gofer_specify.md +0 -855
  292. package/resources/gofer/gemini/commands/gofer/2_gofer_specify.toml +0 -2
  293. package/resources/gofer/gemini/commands/gofer/3_gofer_plan.md +0 -894
  294. package/resources/gofer/gemini/commands/gofer/3_gofer_plan.toml +0 -2
  295. package/resources/gofer/gemini/commands/gofer/4_gofer_tasks.md +0 -755
  296. package/resources/gofer/gemini/commands/gofer/4_gofer_tasks.toml +0 -2
  297. package/resources/gofer/gemini/commands/gofer/5_gofer_implement.md +0 -939
  298. package/resources/gofer/gemini/commands/gofer/5_gofer_implement.toml +0 -2
  299. package/resources/gofer/gemini/commands/gofer/6_gofer_validate.md +0 -2297
  300. package/resources/gofer/gemini/commands/gofer/6_gofer_validate.toml +0 -2
  301. package/resources/gofer/gemini/commands/gofer/7_gofer_save.md +0 -418
  302. package/resources/gofer/gemini/commands/gofer/7_gofer_save.toml +0 -2
  303. package/resources/gofer/gemini/commands/gofer/7a_stakeholder_comms.md +0 -503
  304. package/resources/gofer/gemini/commands/gofer/7a_stakeholder_comms.toml +0 -2
  305. package/resources/gofer/gemini/commands/gofer/8_gofer_branding.md +0 -310
  306. package/resources/gofer/gemini/commands/gofer/8_gofer_branding.toml +0 -2
  307. package/resources/gofer/gemini/commands/gofer/9_gofer_tests.md +0 -553
  308. package/resources/gofer/gemini/commands/gofer/9_gofer_tests.toml +0 -2
  309. package/resources/gofer/gemini/commands/gofer/gofer_bootstrap_workspace.md +0 -143
  310. package/resources/gofer/gemini/commands/gofer/gofer_bootstrap_workspace.toml +0 -2
  311. package/resources/gofer/gemini/commands/gofer/gofer_check_workspace.md +0 -114
  312. package/resources/gofer/gemini/commands/gofer/gofer_check_workspace.toml +0 -2
  313. package/resources/gofer/gemini/commands/gofer/gofer_constitution.md +0 -607
  314. package/resources/gofer/gemini/commands/gofer/gofer_constitution.toml +0 -2
  315. package/resources/gofer/gemini/commands/gofer/gofer_diagnose.md +0 -109
  316. package/resources/gofer/gemini/commands/gofer/gofer_diagnose.toml +0 -2
  317. package/resources/gofer/gemini/commands/gofer/gofer_eai_first_run.md +0 -392
  318. package/resources/gofer/gemini/commands/gofer/gofer_eai_first_run.toml +0 -2
  319. package/resources/gofer/gemini/commands/gofer/gofer_hydrate.md +0 -556
  320. package/resources/gofer/gemini/commands/gofer/gofer_hydrate.toml +0 -2
  321. package/resources/gofer/gemini/commands/gofer/gofer_personality.md +0 -39
  322. package/resources/gofer/gemini/commands/gofer/gofer_personality.toml +0 -2
  323. package/resources/gofer/gemini/commands/gofer/gofer_plan.md +0 -35
  324. package/resources/gofer/gemini/commands/gofer/gofer_plan.toml +0 -2
  325. package/resources/gofer/gemini/commands/gofer/gofer_side.md +0 -40
  326. package/resources/gofer/gemini/commands/gofer/gofer_side.toml +0 -2
  327. package/resources/gofer/gemini/commands/gofer/gofer_spec_summary.md +0 -107
  328. package/resources/gofer/gemini/commands/gofer/gofer_spec_summary.toml +0 -2
  329. package/resources/gofer/gemini/commands/gofer/gofer_tdd.md +0 -107
  330. package/resources/gofer/gemini/commands/gofer/gofer_tdd.toml +0 -2
  331. package/resources/gofer/gemini/commands/gofer/gofer_vocabulary.md +0 -107
  332. package/resources/gofer/gemini/commands/gofer/gofer_vocabulary.toml +0 -2
  333. package/resources/gofer/gemini/commands/gofer/gofer_zoom_out.md +0 -107
  334. package/resources/gofer/gemini/commands/gofer/gofer_zoom_out.toml +0 -2
  335. package/resources/gofer/github-skills/eai-gofer/SKILL.md +0 -56
  336. package/resources/gofer/system-skills/0_gofer_start/SKILL.md +0 -1287
  337. package/resources/gofer/system-skills/0a_problem_validation/SKILL.md +0 -429
  338. package/resources/gofer/system-skills/10_gofer_cloud/SKILL.md +0 -635
  339. package/resources/gofer/system-skills/1_gofer_research/SKILL.md +0 -1159
  340. package/resources/gofer/system-skills/2_gofer_specify/SKILL.md +0 -860
  341. package/resources/gofer/system-skills/3_gofer_plan/SKILL.md +0 -899
  342. package/resources/gofer/system-skills/4_gofer_tasks/SKILL.md +0 -760
  343. package/resources/gofer/system-skills/5_gofer_implement/SKILL.md +0 -944
  344. package/resources/gofer/system-skills/6_gofer_validate/SKILL.md +0 -2302
  345. package/resources/gofer/system-skills/7_gofer_save/SKILL.md +0 -423
  346. package/resources/gofer/system-skills/7a_stakeholder_comms/SKILL.md +0 -508
  347. package/resources/gofer/system-skills/8_gofer_branding/SKILL.md +0 -315
  348. package/resources/gofer/system-skills/9_gofer_tests/SKILL.md +0 -558
  349. package/resources/gofer/system-skills/gofer_bootstrap_workspace/SKILL.md +0 -148
  350. package/resources/gofer/system-skills/gofer_check_workspace/SKILL.md +0 -119
  351. package/resources/gofer/system-skills/gofer_constitution/SKILL.md +0 -612
  352. package/resources/gofer/system-skills/gofer_diagnose/SKILL.md +0 -114
  353. package/resources/gofer/system-skills/gofer_eai_first_run/SKILL.md +0 -397
  354. package/resources/gofer/system-skills/gofer_hydrate/SKILL.md +0 -561
  355. package/resources/gofer/system-skills/gofer_personality/SKILL.md +0 -44
  356. package/resources/gofer/system-skills/gofer_plan/SKILL.md +0 -40
  357. package/resources/gofer/system-skills/gofer_side/SKILL.md +0 -45
  358. package/resources/gofer/system-skills/gofer_spec_summary/SKILL.md +0 -112
  359. package/resources/gofer/system-skills/gofer_tdd/SKILL.md +0 -112
  360. package/resources/gofer/system-skills/gofer_vocabulary/SKILL.md +0 -112
  361. package/resources/gofer/system-skills/gofer_zoom_out/SKILL.md +0 -112
  362. 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
+ });