forge-workflow 0.1.0-beta.5 → 0.1.0-beta.7

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 (268) hide show
  1. package/AGENTS.md +4 -0
  2. package/CHANGELOG.md +48 -0
  3. package/CLAUDE.md +0 -12
  4. package/CODING_STANDARDS.md +72 -0
  5. package/bin/forge.js +12 -1
  6. package/docs/guides/MIGRATION.md +3 -3
  7. package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +4 -0
  8. package/docs/reference/INSTALL.md +4 -0
  9. package/docs/reference/LEGACY_CLAIM_REPAIR.md +112 -0
  10. package/docs/reference/RELEASE.md +4 -4
  11. package/docs/reference/github-accounts.md +134 -0
  12. package/docs/reference/shepherd.md +63 -13
  13. package/lib/adapters/pr-state-adapter.js +15 -2
  14. package/lib/base-remote.js +138 -0
  15. package/lib/beta5-compatibility-evidence.js +1093 -0
  16. package/lib/bun-lockfile-proof.js +413 -0
  17. package/lib/bun-workflow-pins.js +461 -0
  18. package/lib/capabilities/index.js +9 -0
  19. package/lib/capabilities/model.js +141 -0
  20. package/lib/capabilities/probes.js +347 -0
  21. package/lib/codex-skills.js +2 -2
  22. package/lib/commands/_manifest.js +1 -0
  23. package/lib/commands/_registry.js +48 -18
  24. package/lib/commands/clean.js +57 -1
  25. package/lib/commands/doctor.js +37 -6
  26. package/lib/commands/gate.js +197 -27
  27. package/lib/commands/github.js +215 -0
  28. package/lib/commands/hooks.js +54 -6
  29. package/lib/commands/memory.js +66 -2
  30. package/lib/commands/merge.js +720 -73
  31. package/lib/commands/plan.js +33 -2
  32. package/lib/commands/pr.js +2 -0
  33. package/lib/commands/preflight.js +10 -2
  34. package/lib/commands/push.js +120 -9
  35. package/lib/commands/recall.js +95 -61
  36. package/lib/commands/release.js +23 -2
  37. package/lib/commands/remember.js +28 -4
  38. package/lib/commands/serve.js +26 -9
  39. package/lib/commands/setup.js +132 -4
  40. package/lib/commands/shepherd.js +578 -72
  41. package/lib/commands/ship.js +15 -69
  42. package/lib/commands/skill.js +8 -0
  43. package/lib/commands/team.js +47 -8
  44. package/lib/commands/test.js +163 -4
  45. package/lib/commands/validate.js +78 -22
  46. package/lib/commands/worktree.js +155 -19
  47. package/lib/fixtures/beta5-corpus/v1/README.md +9 -0
  48. package/lib/fixtures/beta5-corpus/v1/contract/command-contract.json +26 -0
  49. package/lib/fixtures/beta5-corpus/v1/contract/package-contract.json +13 -0
  50. package/lib/fixtures/beta5-corpus/v1/contract/workflow-stage-matrix.json +8 -0
  51. package/lib/fixtures/beta5-corpus/v1/manifest.json +25 -0
  52. package/lib/fixtures/beta5-corpus/v1/state/comments.jsonl +1 -0
  53. package/lib/fixtures/beta5-corpus/v1/state/config.yaml +6 -0
  54. package/lib/fixtures/beta5-corpus/v1/state/dependencies.jsonl +1 -0
  55. package/lib/fixtures/beta5-corpus/v1/state/issues.jsonl +2 -0
  56. package/lib/fixtures/beta5-corpus/v1/state/kernel.sql +20 -0
  57. package/lib/forge-issues.js +78 -0
  58. package/lib/gate-events.js +98 -10
  59. package/lib/github-context.js +308 -0
  60. package/lib/global-flags.js +1 -0
  61. package/lib/hook-renderer.js +29 -1
  62. package/lib/issue-render.js +19 -0
  63. package/lib/kernel/broker.js +723 -31
  64. package/lib/kernel/claim-reconciler.js +238 -0
  65. package/lib/kernel/lease-enforcer.js +9 -4
  66. package/lib/kernel/legacy-claim-repair.js +442 -0
  67. package/lib/kernel/live-claim-projection.js +26 -0
  68. package/lib/kernel/migrations.js +118 -3
  69. package/lib/kernel/readiness-model.js +184 -12
  70. package/lib/kernel/schema.js +49 -1
  71. package/lib/kernel/sqlite-driver.js +3322 -183
  72. package/lib/kernel/taxonomy-validator.js +4 -1
  73. package/lib/kernel/windows-private-acl.js +239 -0
  74. package/lib/memory/hygiene.js +191 -0
  75. package/lib/memory/router.js +94 -27
  76. package/lib/memory/usage-evidence.js +4 -0
  77. package/lib/memory-digest.js +59 -0
  78. package/lib/merge-rules.js +135 -17
  79. package/lib/npm-publish-workflow.js +233 -40
  80. package/lib/package-root.js +2 -0
  81. package/lib/pr-monitor/auto-actions.js +169 -28
  82. package/lib/pr-monitor/differ.js +110 -4
  83. package/lib/pr-monitor/events.js +0 -0
  84. package/lib/pr-monitor/flow-monitor.js +1424 -0
  85. package/lib/pr-monitor/gather.js +251 -44
  86. package/lib/pr-monitor/journal.js +0 -37
  87. package/lib/pr-monitor/monitor.js +117 -10
  88. package/lib/pr-monitor/process-identity.js +117 -0
  89. package/lib/pr-monitor/reconcile-executor.js +1101 -625
  90. package/lib/pr-monitor/reconcile.js +0 -0
  91. package/lib/pr-monitor/render-summary.js +121 -24
  92. package/lib/pr-monitor/review-preflight.js +269 -0
  93. package/lib/pr-monitor/shepherd-lease.js +28 -19
  94. package/lib/pr-monitor/verdict.js +438 -0
  95. package/lib/pr-monitor/watch-lifecycle.js +144 -38
  96. package/lib/pr-monitor/watch-owner.js +1414 -0
  97. package/lib/pr-monitor/watch.js +129 -58
  98. package/lib/pr-shepherd.js +17 -3
  99. package/lib/project-memory.js +145 -3
  100. package/lib/protected-state-authority.js +799 -4
  101. package/lib/protected-state-surfaces.js +181 -3
  102. package/lib/release-readiness.js +2 -3
  103. package/lib/review-adapter.js +65 -0
  104. package/lib/skills-sync.js +65 -32
  105. package/lib/validation/risk-manifest.js +339 -0
  106. package/lib/validation-receipt.js +190 -0
  107. package/lib/workflow/enforce-stage.js +44 -0
  108. package/lib/workflow/plan-authority.js +225 -0
  109. package/node_modules/@forge/contracts/compatibility-matrix.v1.json +1 -0
  110. package/node_modules/@forge/contracts/contract-baseline.v1.json +134 -0
  111. package/node_modules/@forge/contracts/fixtures/v1/canonical-hash.json +1 -0
  112. package/node_modules/@forge/contracts/fixtures/v1/contract-inputs.v1.json +71 -0
  113. package/node_modules/@forge/contracts/fixtures/v1/feedback-secret-reject.json +1 -0
  114. package/node_modules/@forge/contracts/fixtures/v1/identity-conflict.json +1 -0
  115. package/node_modules/@forge/contracts/fixtures/v1/malformed-envelope-inputs.v1.json +16 -0
  116. package/node_modules/@forge/contracts/fixtures/v1/malformed-missing-required.json +1 -0
  117. package/node_modules/@forge/contracts/fixtures/v1/monitor-bounds-reject.json +1 -0
  118. package/node_modules/@forge/contracts/fixtures/v1/monitor-receipt-privacy-reject.json +1 -0
  119. package/node_modules/@forge/contracts/fixtures/v1/not-executed-receipt.json +1 -0
  120. package/node_modules/@forge/contracts/fixtures/v1/privacy-redaction.json +1 -0
  121. package/node_modules/@forge/contracts/fixtures/v1/retry-identical.json +1 -0
  122. package/node_modules/@forge/contracts/fixtures/v1/stale-authority-reject.json +1 -0
  123. package/node_modules/@forge/contracts/fixtures/v1/structured-error-privacy-reject.json +1 -0
  124. package/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-live-reject.json +1 -0
  125. package/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-test-reject.json +1 -0
  126. package/node_modules/@forge/contracts/fixtures/v1/unknown-advisory-roundtrip.json +1 -0
  127. package/node_modules/@forge/contracts/fixtures/v1/unknown-consequential-reject.json +1 -0
  128. package/node_modules/@forge/contracts/fixtures/v1/valid-full.json +1 -0
  129. package/node_modules/@forge/contracts/fixtures/v1/valid-minimal.json +1 -0
  130. package/node_modules/@forge/contracts/fixtures/v1/wrong-capability-digest.json +1 -0
  131. package/node_modules/@forge/contracts/index.js +32 -0
  132. package/node_modules/@forge/contracts/package.json +35 -0
  133. package/node_modules/@forge/contracts/schemas/v1/capability-manifest.schema.json +1 -0
  134. package/node_modules/@forge/contracts/schemas/v1/claim-request.schema.json +1 -0
  135. package/node_modules/@forge/contracts/schemas/v1/context-packet.schema.json +1 -0
  136. package/node_modules/@forge/contracts/schemas/v1/delivery-receipt.schema.json +1 -0
  137. package/node_modules/@forge/contracts/schemas/v1/feedback-report.schema.json +1 -0
  138. package/node_modules/@forge/contracts/schemas/v1/lease-receipt.schema.json +1 -0
  139. package/node_modules/@forge/contracts/schemas/v1/monitor-event.schema.json +1 -0
  140. package/node_modules/@forge/contracts/schemas/v1/monitor-receipt.schema.json +1 -0
  141. package/node_modules/@forge/contracts/schemas/v1/run-receipt.schema.json +1 -0
  142. package/node_modules/@forge/contracts/schemas/v1/structured-error.schema.json +1 -0
  143. package/node_modules/@forge/contracts/schemas/v1/work-packet.schema.json +1 -0
  144. package/node_modules/@forge/contracts/src/baseline.js +23 -0
  145. package/node_modules/@forge/contracts/src/canonical.js +151 -0
  146. package/node_modules/@forge/contracts/src/definitions.js +176 -0
  147. package/node_modules/@forge/contracts/src/identity.js +42 -0
  148. package/node_modules/@forge/contracts/src/schema.js +72 -0
  149. package/node_modules/@forge/contracts/src/validate.js +305 -0
  150. package/node_modules/@forge/flow/index.js +114 -0
  151. package/node_modules/@forge/flow/package.json +34 -0
  152. package/node_modules/@forge/flow/src/bounded-loop.js +415 -0
  153. package/node_modules/@forge/flow/src/efficiency-supervisor.js +89 -0
  154. package/node_modules/@forge/flow/src/executor.js +279 -0
  155. package/node_modules/@forge/flow/src/monitor-durability.js +419 -0
  156. package/node_modules/@forge/flow/src/monitor-runtime.js +460 -0
  157. package/node_modules/@forge/flow/src/process-lifecycle.js +469 -0
  158. package/node_modules/@forge/flow/src/skill-runtime.js +343 -0
  159. package/node_modules/@forge/memory/index.js +331 -0
  160. package/node_modules/@forge/memory/package.json +34 -0
  161. package/node_modules/@forge/memory/src/authority-provider.js +67 -0
  162. package/node_modules/@forge/memory/src/backend-registry.js +166 -0
  163. package/node_modules/@forge/memory/src/feedback-intake.js +243 -0
  164. package/node_modules/@forge/memory/src/pr-lifecycle-authority.js +946 -0
  165. package/node_modules/@forge/memory/src/usage-evidence.js +205 -0
  166. package/package.json +16 -4
  167. package/packages/flow/node_modules/@forge/contracts/compatibility-matrix.v1.json +1 -0
  168. package/packages/flow/node_modules/@forge/contracts/contract-baseline.v1.json +134 -0
  169. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/canonical-hash.json +1 -0
  170. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/contract-inputs.v1.json +71 -0
  171. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/feedback-secret-reject.json +1 -0
  172. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/identity-conflict.json +1 -0
  173. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/malformed-envelope-inputs.v1.json +16 -0
  174. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/malformed-missing-required.json +1 -0
  175. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/monitor-bounds-reject.json +1 -0
  176. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/monitor-receipt-privacy-reject.json +1 -0
  177. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/not-executed-receipt.json +1 -0
  178. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/privacy-redaction.json +1 -0
  179. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/retry-identical.json +1 -0
  180. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/stale-authority-reject.json +1 -0
  181. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/structured-error-privacy-reject.json +1 -0
  182. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-live-reject.json +1 -0
  183. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-test-reject.json +1 -0
  184. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/unknown-advisory-roundtrip.json +1 -0
  185. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/unknown-consequential-reject.json +1 -0
  186. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/valid-full.json +1 -0
  187. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/valid-minimal.json +1 -0
  188. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/wrong-capability-digest.json +1 -0
  189. package/packages/flow/node_modules/@forge/contracts/index.js +32 -0
  190. package/packages/flow/node_modules/@forge/contracts/package.json +35 -0
  191. package/packages/flow/node_modules/@forge/contracts/schemas/v1/capability-manifest.schema.json +1 -0
  192. package/packages/flow/node_modules/@forge/contracts/schemas/v1/claim-request.schema.json +1 -0
  193. package/packages/flow/node_modules/@forge/contracts/schemas/v1/context-packet.schema.json +1 -0
  194. package/packages/flow/node_modules/@forge/contracts/schemas/v1/delivery-receipt.schema.json +1 -0
  195. package/packages/flow/node_modules/@forge/contracts/schemas/v1/feedback-report.schema.json +1 -0
  196. package/packages/flow/node_modules/@forge/contracts/schemas/v1/lease-receipt.schema.json +1 -0
  197. package/packages/flow/node_modules/@forge/contracts/schemas/v1/monitor-event.schema.json +1 -0
  198. package/packages/flow/node_modules/@forge/contracts/schemas/v1/monitor-receipt.schema.json +1 -0
  199. package/packages/flow/node_modules/@forge/contracts/schemas/v1/run-receipt.schema.json +1 -0
  200. package/packages/flow/node_modules/@forge/contracts/schemas/v1/structured-error.schema.json +1 -0
  201. package/packages/flow/node_modules/@forge/contracts/schemas/v1/work-packet.schema.json +1 -0
  202. package/packages/flow/node_modules/@forge/contracts/src/baseline.js +23 -0
  203. package/packages/flow/node_modules/@forge/contracts/src/canonical.js +151 -0
  204. package/packages/flow/node_modules/@forge/contracts/src/definitions.js +176 -0
  205. package/packages/flow/node_modules/@forge/contracts/src/identity.js +42 -0
  206. package/packages/flow/node_modules/@forge/contracts/src/schema.js +72 -0
  207. package/packages/flow/node_modules/@forge/contracts/src/validate.js +305 -0
  208. package/packages/memory/node_modules/@forge/contracts/compatibility-matrix.v1.json +1 -0
  209. package/packages/memory/node_modules/@forge/contracts/contract-baseline.v1.json +134 -0
  210. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/canonical-hash.json +1 -0
  211. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/contract-inputs.v1.json +71 -0
  212. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/feedback-secret-reject.json +1 -0
  213. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/identity-conflict.json +1 -0
  214. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/malformed-envelope-inputs.v1.json +16 -0
  215. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/malformed-missing-required.json +1 -0
  216. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/monitor-bounds-reject.json +1 -0
  217. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/monitor-receipt-privacy-reject.json +1 -0
  218. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/not-executed-receipt.json +1 -0
  219. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/privacy-redaction.json +1 -0
  220. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/retry-identical.json +1 -0
  221. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/stale-authority-reject.json +1 -0
  222. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/structured-error-privacy-reject.json +1 -0
  223. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-live-reject.json +1 -0
  224. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-test-reject.json +1 -0
  225. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/unknown-advisory-roundtrip.json +1 -0
  226. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/unknown-consequential-reject.json +1 -0
  227. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/valid-full.json +1 -0
  228. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/valid-minimal.json +1 -0
  229. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/wrong-capability-digest.json +1 -0
  230. package/packages/memory/node_modules/@forge/contracts/index.js +32 -0
  231. package/packages/memory/node_modules/@forge/contracts/package.json +35 -0
  232. package/packages/memory/node_modules/@forge/contracts/schemas/v1/capability-manifest.schema.json +1 -0
  233. package/packages/memory/node_modules/@forge/contracts/schemas/v1/claim-request.schema.json +1 -0
  234. package/packages/memory/node_modules/@forge/contracts/schemas/v1/context-packet.schema.json +1 -0
  235. package/packages/memory/node_modules/@forge/contracts/schemas/v1/delivery-receipt.schema.json +1 -0
  236. package/packages/memory/node_modules/@forge/contracts/schemas/v1/feedback-report.schema.json +1 -0
  237. package/packages/memory/node_modules/@forge/contracts/schemas/v1/lease-receipt.schema.json +1 -0
  238. package/packages/memory/node_modules/@forge/contracts/schemas/v1/monitor-event.schema.json +1 -0
  239. package/packages/memory/node_modules/@forge/contracts/schemas/v1/monitor-receipt.schema.json +1 -0
  240. package/packages/memory/node_modules/@forge/contracts/schemas/v1/run-receipt.schema.json +1 -0
  241. package/packages/memory/node_modules/@forge/contracts/schemas/v1/structured-error.schema.json +1 -0
  242. package/packages/memory/node_modules/@forge/contracts/schemas/v1/work-packet.schema.json +1 -0
  243. package/packages/memory/node_modules/@forge/contracts/src/baseline.js +23 -0
  244. package/packages/memory/node_modules/@forge/contracts/src/canonical.js +151 -0
  245. package/packages/memory/node_modules/@forge/contracts/src/definitions.js +176 -0
  246. package/packages/memory/node_modules/@forge/contracts/src/identity.js +42 -0
  247. package/packages/memory/node_modules/@forge/contracts/src/schema.js +72 -0
  248. package/packages/memory/node_modules/@forge/contracts/src/validate.js +305 -0
  249. package/scripts/commitlint.js +13 -15
  250. package/scripts/generate-risk-manifest.js +91 -0
  251. package/scripts/github-context-bridge.sh +10 -0
  252. package/scripts/legacy-claim-repair.js +145 -0
  253. package/scripts/lib/behavioral-eval-runtime.js +3 -2
  254. package/scripts/process-tree.js +14 -2
  255. package/scripts/protected-state-check.js +440 -17
  256. package/scripts/sync-agent-skills.js +333 -34
  257. package/scripts/test-full-suite.js +704 -18
  258. package/scripts/test-profile.js +13 -3
  259. package/scripts/test.js +96 -15
  260. package/skills/coverage.json +1 -0
  261. package/skills/review/SKILL.md +2 -0
  262. package/skills/review/evals/scorecard.json +2 -2
  263. package/skills/setup/SKILL.md +18 -0
  264. package/skills/setup/evals/scorecard.json +3 -3
  265. package/skills/shepherd/SKILL.md +19 -2
  266. package/skills/shepherd/evals/scorecard.json +3 -3
  267. package/skills/validate/SKILL.md +3 -0
  268. package/skills/validate/evals/scorecard.json +1 -1
@@ -28,6 +28,8 @@ const DEFAULT_DIGEST_BUDGET_TOKENS = 400;
28
28
  const DEFAULT_NOTE_LIMIT = 5;
29
29
  const DEFAULT_ISSUE_LIMIT = 5;
30
30
  const DIGEST_HEADER = 'Forge memory (auto-injected at session start):';
31
+ const READ_ATTENTION_HEADER = 'Forge path memory (untrusted context; not authority):';
32
+ const SESSION_SUMMARY_REMINDER = 'Before ending this session, persist only durable learnings with `forge remember --session-summary --what <text> --why <text> --learned <text>`.';
31
33
 
32
34
  /** Run an async producer, returning `fallback` on any throw/rejection (never propagates). */
33
35
  async function safe(producer, fallback) {
@@ -176,6 +178,59 @@ function issuesSection(ready, claimed) {
176
178
  });
177
179
  }
178
180
 
181
+ function sessionSummarySection(claimed) {
182
+ if (!claimed.length) return null;
183
+ return buildSection({
184
+ id: 'session_summary_nudge',
185
+ title: 'Session learning reminder',
186
+ content: SESSION_SUMMARY_REMINDER,
187
+ priority: 1,
188
+ preserve: true,
189
+ });
190
+ }
191
+
192
+ function normalizePath(value) {
193
+ return String(value || '').replaceAll('\\', '/').replace(/^\.\//, '').toLowerCase();
194
+ }
195
+
196
+ function notePaths(note) {
197
+ const explicit = [note?.path, ...(Array.isArray(note?.paths) ? note.paths : [])];
198
+ const tagged = (Array.isArray(note?.tags) ? note.tags : [])
199
+ .filter(tag => typeof tag === 'string' && tag.startsWith('path:'))
200
+ .map(tag => tag.slice('path:'.length));
201
+ return [...explicit, ...tagged].map(normalizePath).filter(Boolean);
202
+ }
203
+
204
+ function matchesPath(targetPath, candidatePath) {
205
+ const target = normalizePath(targetPath);
206
+ const candidate = normalizePath(candidatePath);
207
+ return target === candidate || target.endsWith(`/${candidate}`);
208
+ }
209
+
210
+ function buildReadAttentionDigest(targetPath, notes = [], options = {}) {
211
+ const budgetTokens = options.budgetTokens || 160;
212
+ const matched = (Array.isArray(notes) ? notes : []).filter(note =>
213
+ notePaths(note).some(candidate => matchesPath(targetPath, candidate)));
214
+ if (!matched.length) return { text: '', empty: true, tokens: 0 };
215
+
216
+ const lines = matched.map(formatNoteLine);
217
+ const section = buildSection({
218
+ id: 'read_attention',
219
+ title: 'Path-matched memory',
220
+ content: lines.join('\n'),
221
+ priority: 1,
222
+ preserve: false,
223
+ untrustedSource: 'path-memory',
224
+ });
225
+ const budgeted = applyBudget([section], Math.max(1, budgetTokens - estimateTokens(READ_ATTENTION_HEADER) - 20));
226
+ const content = budgeted.sections[0]?.content;
227
+ if (!content) return { text: '', empty: true, tokens: 0 };
228
+ const text = `${READ_ATTENTION_HEADER}\n\n${fenceUntrusted(content, { source: 'path-memory' })}`;
229
+ const tokens = estimateTokens(text);
230
+ if (tokens > budgetTokens) return { text: '', empty: true, tokens: 0 };
231
+ return { text, empty: false, tokens };
232
+ }
233
+
179
234
  /**
180
235
  * Assemble the bounded digest text. PURE. Never exceeds `budgetTokens` (delegated to
181
236
  * applyBudget). Empty inputs → { text: '', empty: true } so the caller injects nothing.
@@ -194,6 +249,7 @@ function buildMemoryDigest(data = {}, options = {}) {
194
249
  // Inbox (priority 5) is a THIRD section beside notes + issues; a fresh human directive
195
250
  // outranks stale notes (10) and the agent's own issue list (20) under budget pressure.
196
251
  const sections = [
252
+ sessionSummarySection(claimed),
197
253
  inboxSection(inbox),
198
254
  ...notesSections(notes, budgetTokens),
199
255
  issuesSection(ready, claimed),
@@ -217,7 +273,10 @@ function buildMemoryDigest(data = {}, options = {}) {
217
273
  module.exports = {
218
274
  DEFAULT_DIGEST_BUDGET_TOKENS,
219
275
  DIGEST_HEADER,
276
+ READ_ATTENTION_HEADER,
277
+ SESSION_SUMMARY_REMINDER,
220
278
  buildMemoryDigest,
279
+ buildReadAttentionDigest,
221
280
  collectDigestData,
222
281
  extractIssues,
223
282
  // exported for focused reuse / tests
@@ -21,7 +21,8 @@
21
21
  *
22
22
  * PR context shape (all fields optional; absence is treated fail-closed by the
23
23
  * rules that need them):
24
- * - checks: Array<{ name, conclusion }> CI check rollup
24
+ * - checks: Array<{ name, appId, status, conclusion } | { name, appId:null, state }>
25
+ * - requiredChecks: Array<{ context, appId }> protected required set
25
26
  * - requiredChecksKnown: boolean is the required set readable?
26
27
  * - unresolvedThreads: number | Array open review threads
27
28
  * - behindBase: number | boolean commits behind base
@@ -34,7 +35,7 @@
34
35
  * - state: string (OPEN | MERGED | CLOSED) read by the command pre-flight, not a rule
35
36
  *
36
37
  * Built-in rule types (compose as a list, ANDed by default):
37
- * checks_green | threads_resolved | not_behind | no_conflicts | not_draft |
38
+ * verdict_clean | checks_green | threads_resolved | not_behind | no_conflicts | not_draft |
38
39
  * min_approvals:N | settle_min:N | idle_min:N | last_comment_by:X |
39
40
  * approved_by:[..] | not_commented_by:[..]
40
41
  * `checks_green` is bare (ALL checks must be green) or scoped:
@@ -48,14 +49,21 @@
48
49
  * documented follow-up and intentionally NOT implemented here.
49
50
  *
50
51
  * Follow-ups (documented, NOT built here): an opt-in `auto_update` executor
51
- * (update-branch when behind → wait for CI → re-check → merge); required-checks
52
- * SCOPING for `checks_green` (read the branch-protection required set instead of
53
- * requiring every rollup check green); a configurable merge `method`
52
+ * (update-branch when behind → wait for CI → re-check → merge); a configurable merge `method`
54
53
  * (squash | merge | rebase); and post-merge branch deletion.
55
54
  */
56
55
 
57
56
  const SUCCESS_CONCLUSIONS = new Set(['SUCCESS']);
57
+ const SAFE_OPTIONAL_CONCLUSIONS = new Set(['SUCCESS', 'NEUTRAL', 'SKIPPED']);
58
58
  const MS_PER_MIN = 60_000;
59
+ const {
60
+ normalizePrNumber,
61
+ normalizeRepository,
62
+ normalizeSha,
63
+ object,
64
+ readArrayItems,
65
+ readDataProperty,
66
+ } = require('./pr-monitor/verdict');
59
67
 
60
68
  /** Coerce an epoch-ms number, a Date, or an ISO string to epoch ms (NaN if unparseable). */
61
69
  function toMs(value) {
@@ -127,16 +135,49 @@ function checkName(check) {
127
135
  return String((check && check.name) || '');
128
136
  }
129
137
 
138
+ function isCheckObservationWellFormed(check) {
139
+ if (!check || typeof check !== 'object' || typeof check.name !== 'string' || !check.name.trim()
140
+ || !Object.prototype.hasOwnProperty.call(check, 'appId')) return false;
141
+ const hasState = Object.prototype.hasOwnProperty.call(check, 'state');
142
+ const hasStatus = Object.prototype.hasOwnProperty.call(check, 'status');
143
+ const hasConclusion = Object.prototype.hasOwnProperty.call(check, 'conclusion');
144
+ if (hasState) {
145
+ return !hasStatus && !hasConclusion && check.appId === null
146
+ && typeof check.state === 'string' && Boolean(check.state.trim());
147
+ }
148
+ return hasStatus && hasConclusion && Number.isInteger(check.appId) && check.appId > 0
149
+ && typeof check.status === 'string' && Boolean(check.status.trim())
150
+ && (check.conclusion === null || typeof check.conclusion === 'string');
151
+ }
152
+
153
+ function normalizeRequiredCheck(check) {
154
+ if (!check || typeof check !== 'object' || typeof check.context !== 'string' || !check.context.trim()
155
+ || !Object.prototype.hasOwnProperty.call(check, 'appId')
156
+ || (check.appId !== null && (!Number.isInteger(check.appId) || check.appId <= 0))) return null;
157
+ return { context: check.context, appId: check.appId };
158
+ }
159
+
160
+ function matchesRequiredCheck(check, required) {
161
+ return checkName(check) === required.context
162
+ && (required.appId === null || check.appId === required.appId);
163
+ }
164
+
165
+ function isSafeOptionalCheck(check) {
166
+ if (Object.prototype.hasOwnProperty.call(check, 'state')) return isCheckGreen(check);
167
+ return String(check.status).toUpperCase() === 'COMPLETED'
168
+ && SAFE_OPTIONAL_CONCLUSIONS.has(String(check.conclusion || '').toUpperCase());
169
+ }
170
+
130
171
  /**
131
- * `checks_green` — every CI check must be green per `isCheckGreen`. Optionally scoped:
132
- * - bare string / no arg → ALL checks must be green (strict default).
133
- * - { ignore: ["A","B"] } → the named checks are EXEMPT; every OTHER check
134
- * must be green. `ignore: []` is identical to the bare form.
172
+ * `checks_green` — protected checks must be strict SUCCESS; optional check runs
173
+ * may also be terminal NEUTRAL or SKIPPED. Optionally scoped:
174
+ * - bare string / no arg → protected checks strict, optional checks safely terminal.
175
+ * - { ignore: ["A","B"] } → the named optional checks are EXEMPT;
176
+ * `ignore: []` is identical to the bare form.
135
177
  * - { only: ["A"] } → ONLY the named checks must be green (present AND
136
- * green per `isCheckGreen`); all others are ignored.
178
+ * green per `isCheckGreen`); all optional others are ignored.
137
179
  * `ignore` + `only` together is malformed → fail-closed. Exact name match.
138
- * (`required_only` — scope to the branch-protection required set — is a
139
- * documented follow-up, not built here.)
180
+ * The protected required set is always enforced before optional scoping.
140
181
  */
141
182
  function checksGreen(ctx, arg) {
142
183
  let ignore = [];
@@ -168,14 +209,42 @@ function checksGreen(ctx, arg) {
168
209
  if (!Array.isArray(ctx.checks)) {
169
210
  return fail('check results are missing from the PR context (fail-closed).');
170
211
  }
212
+ if (!Array.isArray(ctx.requiredChecks)) {
213
+ return fail('required-check set is missing or malformed (fail-closed).');
214
+ }
215
+ if (ctx.checks.some((check) => !isCheckObservationWellFormed(check))) {
216
+ return fail('check results contain malformed observations (fail-closed).');
217
+ }
218
+
219
+ const required = ctx.requiredChecks.map(normalizeRequiredCheck);
220
+ if (required.some((check) => !check)) {
221
+ return fail('required-check set contains malformed entries (fail-closed).');
222
+ }
223
+ const requiredApps = new Map();
224
+ for (const check of required) {
225
+ if (!requiredApps.has(check.context)) requiredApps.set(check.context, new Set());
226
+ requiredApps.get(check.context).add(check.appId === null ? '*' : String(check.appId));
227
+ }
228
+ if ([...requiredApps.values()].some((apps) => apps.size > 1)) {
229
+ return fail('required-check set contains conflicting application identities (fail-closed).');
230
+ }
231
+
232
+ const requiredProblems = required.map((requiredCheck) => {
233
+ const matches = ctx.checks.filter((check) => matchesRequiredCheck(check, requiredCheck));
234
+ if (matches.length === 0) return `${requiredCheck.context} (missing)`;
235
+ if (matches.some((check) => !isCheckGreen(check))) return `${requiredCheck.context} (not successful)`;
236
+ return null;
237
+ }).filter(Boolean);
238
+ if (requiredProblems.length > 0) {
239
+ return fail(`protected required check(s) not green: ${requiredProblems.join(', ')}.`);
240
+ }
171
241
 
172
242
  if (only) {
173
243
  // Only the named checks matter — each must be PRESENT and green.
174
- const byName = new Map(ctx.checks.map((c) => [checkName(c), c]));
175
244
  const problems = only.map((name) => {
176
- const check = byName.get(name);
177
- if (!check) return `${name} (missing)`;
178
- if (!isCheckGreen(check)) return `${name} (${check.conclusion || 'not green'})`;
245
+ const matches = ctx.checks.filter((check) => checkName(check) === name);
246
+ if (matches.length === 0) return `${name} (missing)`;
247
+ if (matches.some((check) => !isCheckGreen(check))) return `${name} (not green)`;
179
248
  return null;
180
249
  }).filter(Boolean);
181
250
  if (problems.length > 0) return fail(`required check(s) not green: ${problems.join(', ')}.`);
@@ -184,7 +253,9 @@ function checksGreen(ctx, arg) {
184
253
 
185
254
  // Default / ignore: every check EXCEPT the exempt ones must be green.
186
255
  const exempt = new Set(ignore);
187
- const notGreen = ctx.checks.filter((c) => !exempt.has(checkName(c)) && !isCheckGreen(c));
256
+ const notGreen = ctx.checks.filter((check) => !exempt.has(checkName(check))
257
+ && !required.some((requiredCheck) => matchesRequiredCheck(check, requiredCheck))
258
+ && !isSafeOptionalCheck(check));
188
259
  if (notGreen.length > 0) {
189
260
  const names = notGreen.map((c) => (c && c.name) || '?').join(', ');
190
261
  return fail(`${notGreen.length} check(s) not green: ${names}.`);
@@ -227,6 +298,52 @@ function notDraft(ctx) {
227
298
  return fail('draft status is unknown (fail-closed).');
228
299
  }
229
300
 
301
+ /** Require the pure reducer's positive verdict to match the caller's immutable head lease. */
302
+ function verdictClean(ctx) {
303
+ const verdict = object(readDataProperty(ctx, 'verdict').value);
304
+ if (!verdict) {
305
+ return fail('current-head verdict is missing or malformed (fail-closed).');
306
+ }
307
+ const repository = normalizeRepository(readDataProperty(ctx, 'repository').value);
308
+ const expectedRepository = normalizeRepository(readDataProperty(ctx, 'expectedRepository').value);
309
+ const prNumber = normalizePrNumber(readDataProperty(ctx, 'prNumber').value);
310
+ const expectedPrNumber = normalizePrNumber(readDataProperty(ctx, 'expectedPrNumber').value);
311
+ const head = normalizeSha(readDataProperty(ctx, 'headSha').value);
312
+ const expected = normalizeSha(readDataProperty(ctx, 'expectedHeadSha').value);
313
+ const base = normalizeSha(readDataProperty(ctx, 'baseSha').value);
314
+ const expectedBase = normalizeSha(readDataProperty(ctx, 'expectedBaseSha').value);
315
+ const verdictRepository = normalizeRepository(readDataProperty(verdict, 'repository').value);
316
+ const verdictPrNumber = normalizePrNumber(readDataProperty(verdict, 'prNumber').value);
317
+ const verdictHead = normalizeSha(readDataProperty(verdict, 'headSha').value);
318
+ const verdictBase = normalizeSha(readDataProperty(verdict, 'baseSha').value);
319
+ const verdictState = readDataProperty(verdict, 'state').value;
320
+ const verdictReasons = readArrayItems(readDataProperty(verdict, 'reasons').value);
321
+ if (!head || !expected || !base || !expectedBase || !verdictHead || !verdictBase) {
322
+ return fail('exact head lease, verdict head, or verdict base is missing or malformed (fail-closed).');
323
+ }
324
+ if (!repository || !expectedRepository || !prNumber || !expectedPrNumber
325
+ || !verdictRepository || !verdictPrNumber) {
326
+ return fail('repository or PR authority is missing or malformed (fail-closed).');
327
+ }
328
+ if (repository !== expectedRepository || verdictRepository !== repository
329
+ || prNumber !== expectedPrNumber || verdictPrNumber !== prNumber) {
330
+ return fail('current-head verdict does not match the exact repository and PR authority.');
331
+ }
332
+ if (head !== expected || verdictHead !== head) {
333
+ return fail('current-head verdict does not match the exact caller head lease.');
334
+ }
335
+ if (base !== expectedBase || verdictBase !== base) {
336
+ return fail('current-head verdict does not match the exact caller base lease.');
337
+ }
338
+ if (verdictState !== 'MERGE_READY') {
339
+ return fail(`current-head verdict is ${String(verdictState || 'INCOMPLETE')}, not MERGE_READY.`);
340
+ }
341
+ if (!verdictReasons || verdictReasons.length > 0) {
342
+ return fail('MERGE_READY verdict carries incomplete or contradictory reasons (fail-closed).');
343
+ }
344
+ return pass();
345
+ }
346
+
230
347
  function minApprovals(ctx, arg) {
231
348
  const need = Number.parseInt(arg, 10);
232
349
  if (!Number.isFinite(need)) return fail('min_approvals requires a numeric argument, e.g. "min_approvals:2".');
@@ -310,6 +427,7 @@ function notWrapper(ctx, arg) {
310
427
  }
311
428
 
312
429
  const PREDICATES = {
430
+ verdict_clean: (ctx) => verdictClean(ctx),
313
431
  checks_green: (ctx, arg) => checksGreen(ctx, arg),
314
432
  threads_resolved: (ctx) => threadsResolved(ctx),
315
433
  not_behind: (ctx) => notBehind(ctx),