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

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 (119) hide show
  1. package/AGENTS.md +14 -7
  2. package/CHANGELOG.md +43 -1
  3. package/README.md +6 -2
  4. package/bin/forge-cmd.js +20 -0
  5. package/bin/forge.js +16 -374
  6. package/docs/INDEX.md +1 -1
  7. package/docs/guides/BEADS_GITHUB_SYNC.md +2 -31
  8. package/docs/guides/MIGRATION.md +4 -4
  9. package/docs/guides/SETUP.md +16 -16
  10. package/docs/reference/COMMANDS.md +8 -5
  11. package/docs/reference/INSIGHTS_RECAP.md +9 -20
  12. package/docs/reference/RELEASE.md +5 -3
  13. package/docs/reference/TOOLCHAIN.md +8 -0
  14. package/docs/reference/protected-state-surfaces.md +4 -4
  15. package/docs/reference/shepherd.md +54 -25
  16. package/lefthook.yml +12 -0
  17. package/lib/activation/ensure-forge-home.js +33 -15
  18. package/lib/adapters/pr-state-adapter.js +344 -142
  19. package/lib/audit-evidence.js +71 -110
  20. package/lib/capped-jsonl-log.js +236 -0
  21. package/lib/commands/_registry.js +2 -2
  22. package/lib/commands/clean.js +196 -32
  23. package/lib/commands/dev.js +4 -33
  24. package/lib/commands/hooks.js +223 -25
  25. package/lib/commands/insights.js +8 -3
  26. package/lib/commands/merge.js +600 -40
  27. package/lib/commands/pr.js +1 -1
  28. package/lib/commands/preflight.js +11 -2
  29. package/lib/commands/prime.js +21 -8
  30. package/lib/commands/push.js +41 -51
  31. package/lib/commands/recall.js +60 -16
  32. package/lib/commands/recap.js +6 -1
  33. package/lib/commands/release.js +17 -2
  34. package/lib/commands/setup.js +191 -94
  35. package/lib/commands/shepherd.js +13 -1
  36. package/lib/commands/ship.js +22 -23
  37. package/lib/commands/skill.js +119 -11
  38. package/lib/commands/status.js +17 -1
  39. package/lib/commands/test.js +24 -34
  40. package/lib/commands/worktree.js +220 -42
  41. package/lib/core/runtime-graph.js +1 -1
  42. package/lib/doc-assertions.js +297 -0
  43. package/lib/existing-tdd-gate.js +253 -0
  44. package/lib/forge-context.js +1 -4
  45. package/lib/forge-issues.js +56 -32
  46. package/lib/git-defaults.js +56 -0
  47. package/lib/harness-capability-matrix.js +3 -3
  48. package/lib/hook-renderer.js +93 -4
  49. package/lib/insights.js +96 -80
  50. package/lib/kernel/backing-issue.js +14 -2
  51. package/lib/kernel/broker.js +16 -0
  52. package/lib/kernel/cli-broker-factory.js +12 -1
  53. package/lib/kernel/close-on-merge.js +154 -0
  54. package/lib/kernel/fs-class.js +42 -25
  55. package/lib/kernel/sqlite-driver.js +153 -29
  56. package/lib/lefthook-wiring.js +21 -1
  57. package/lib/memory/router.js +16 -1
  58. package/lib/memory-digest.js +47 -15
  59. package/lib/memory-recall-events.js +145 -0
  60. package/lib/memory-recall.js +71 -10
  61. package/lib/merge-rules.js +8 -4
  62. package/lib/npm-publish-workflow.js +272 -0
  63. package/lib/orientation.js +68 -43
  64. package/lib/plugin-catalog.js +14 -4
  65. package/lib/pr-bundle.js +5 -6
  66. package/lib/pr-monitor/journal.js +18 -2
  67. package/lib/pr-monitor/reconcile-executor.js +224 -41
  68. package/lib/pr-monitor/render-summary.js +196 -0
  69. package/lib/pr-monitor/shepherd-lease.js +10 -1
  70. package/lib/pr-monitor/watch-lifecycle.js +13 -1
  71. package/lib/pr-pull.js +33 -14
  72. package/lib/pr-shepherd.js +34 -8
  73. package/lib/preflight/gates.js +65 -18
  74. package/lib/preflight/runner.js +5 -0
  75. package/lib/project-memory.js +33 -1
  76. package/lib/protected-state-authority.js +305 -0
  77. package/lib/protected-state-surfaces.js +64 -44
  78. package/lib/release-readiness.js +51 -4
  79. package/lib/shell-utils.js +1 -1
  80. package/lib/skills-sync.js +6 -3
  81. package/lib/smart-merge.js +28 -4
  82. package/lib/symlink-utils.js +74 -26
  83. package/lib/upgrade-safety.js +39 -0
  84. package/lib/using-forge.js +19 -6
  85. package/package.json +6 -7
  86. package/scripts/doc-asserting-tests.js +158 -0
  87. package/scripts/lib/behavioral-eval-runner.js +310 -0
  88. package/scripts/lib/behavioral-eval-runtime.js +456 -0
  89. package/scripts/lib/eval-evidence.js +328 -0
  90. package/scripts/lib/eval-runner.js +81 -41
  91. package/scripts/lib/immutable-eval-corpus.js +309 -0
  92. package/scripts/lib/promotion-evidence-loader.js +94 -0
  93. package/scripts/lib/promotion-scorecard.js +314 -0
  94. package/scripts/npm-release-receipt.js +134 -0
  95. package/scripts/process-tree.js +761 -0
  96. package/scripts/protected-state-check.js +47 -22
  97. package/scripts/run-command-eval.js +29 -1
  98. package/scripts/sync-d20-audit.js +172 -0
  99. package/scripts/test-full-suite.js +249 -37
  100. package/scripts/test.js +176 -43
  101. package/skills/review/SKILL.md +4 -11
  102. package/skills/review/evals/scorecard.json +3 -3
  103. package/skills/rollback/SKILL.md +4 -11
  104. package/skills/rollback/evals/scorecard.json +3 -3
  105. package/skills/shepherd/SKILL.md +20 -14
  106. package/skills/shepherd/evals/scorecard.json +2 -2
  107. package/skills/ship/SKILL.md +4 -12
  108. package/skills/ship/evals/scorecard.json +3 -3
  109. package/skills/worktree/SKILL.md +6 -1
  110. package/skills/worktree/evals/scorecard.json +2 -2
  111. package/lib/beads-setup.js +0 -538
  112. package/lib/beads-sync-scaffold.js +0 -189
  113. package/lib/pat-setup.js +0 -207
  114. package/lib/pr-monitor/render-sticky.js +0 -206
  115. package/lib/pr-monitor/upsert-sticky.js +0 -169
  116. package/scripts/beads-context.sh +0 -577
  117. package/scripts/beads-migrate-to-dolt.sh +0 -7
  118. package/scripts/beads-upgrade-smoke.sh +0 -284
  119. package/scripts/lib/beads-migrate-to-dolt.mjs +0 -503
@@ -1,169 +0,0 @@
1
- 'use strict';
2
-
3
- /**
4
- * PR-monitor sticky-comment upsert — race-safe, converges to EXACTLY ONE sticky
5
- * comment per PR even under a concurrent burst of workflow runs.
6
- *
7
- * Why this exists: the pr-monitor workflow deliberately has NO `concurrency:`
8
- * group. A per-PR group does not help — GitHub's queue replacement cancels the
9
- * previously-PENDING run in a group UNCONDITIONALLY (independent of
10
- * cancel-in-progress), and this workflow's triggers (check_suite:completed fires
11
- * ~10+ times per push, plus reviews/comments) burst hard, so a group left a trail
12
- * of CANCELLED runs that render as red/non-SUCCESS checks and tripped merge-gate
13
- * tooling (kernel issue 97e6a146). Dropping the group removes the cancellations,
14
- * but then two concurrent first-runs on a PR with no sticky yet would BOTH find
15
- * nothing and BOTH create one → duplicate sticky comments. This module closes
16
- * that race deterministically instead.
17
- *
18
- * Reconcile-to-one algorithm:
19
- * 1. List marker comments. If none, create one, then RE-LIST (a concurrent run
20
- * may have created its own in the same burst).
21
- * 2. Pick the deterministic survivor: the LOWEST comment id (oldest). Every
22
- * concurrent run picks the SAME survivor, so they never fight.
23
- * 3. Update the survivor with the latest body; delete every other marker
24
- * comment. Deletes are idempotent (a 404 means a peer already removed it).
25
- *
26
- * A create that lands AFTER a run's re-list is self-healed by the next event:
27
- * every run reconciles to one, and events keep arriving, so the PR converges to a
28
- * single sticky comment.
29
- *
30
- * @module pr-monitor/upsert-sticky
31
- */
32
-
33
- const { execFileSync } = require('node:child_process');
34
-
35
- /** Ids of comments whose body carries the sticky marker. */
36
- function markerCommentIds(comments, marker) {
37
- return (Array.isArray(comments) ? comments : [])
38
- .filter((comment) => typeof comment.body === 'string' && comment.body.includes(marker))
39
- .map((comment) => comment.id);
40
- }
41
-
42
- /** Ascending by numeric id, so the survivor (index 0) is the oldest comment. */
43
- function sortIdsAscending(ids) {
44
- return [...ids].sort((left, right) => Number(left) - Number(right));
45
- }
46
-
47
- /**
48
- * Drive a client to exactly one sticky comment. `client` abstracts the GitHub
49
- * calls so the reconcile logic is unit-testable without the network:
50
- * - list() → array of { id, body }
51
- * - create() → create a new sticky comment (body supplied by the client)
52
- * - update(id) → overwrite comment `id` with the latest body
53
- * - remove(id) → delete comment `id` (must tolerate an already-deleted 404)
54
- *
55
- * @returns {Promise<{ survivor: (number|string|null), deleted: Array<number|string> }>}
56
- */
57
- async function upsertStickyComment({ marker }, client) {
58
- let ids = markerCommentIds(await client.list(), marker);
59
-
60
- if (ids.length === 0) {
61
- await client.create();
62
- // Re-list: a concurrent run may have created its own sticky in this burst.
63
- ids = markerCommentIds(await client.list(), marker);
64
- }
65
-
66
- if (ids.length === 0) {
67
- // The just-created comment is not visible yet (eventual consistency); its
68
- // body is already correct, and the next event will reconcile if a peer raced.
69
- return { survivor: null, deleted: [] };
70
- }
71
-
72
- ids = sortIdsAscending(ids);
73
- const survivor = ids[0];
74
- await client.update(survivor);
75
-
76
- const deleted = [];
77
- for (const id of ids.slice(1)) {
78
- await client.remove(id);
79
- deleted.push(id);
80
- }
81
-
82
- return { survivor, deleted };
83
- }
84
-
85
- // Single choke point for the `gh` CLI, matching lib/commands/merge.js. `gh` is a
86
- // hardcoded literal (never user input) and args are an array (no shell), so the
87
- // S4036 PATH-search finding is a false positive in this developer-tool context;
88
- // one annotation here covers every call site. `encoding: 'utf8'` also pipes
89
- // stderr onto the thrown error, so isAlreadyGone() below can classify failures.
90
- function runGh(args) {
91
- return execFileSync('gh', args, { encoding: 'utf8', windowsHide: true }); // NOSONAR S4036 - hardcoded CLI (gh), args array (no shell), developer-tool context. windowsHide: sticky upsert runs in the detached watcher (issue 931e7924).
92
- }
93
-
94
- /**
95
- * True only when a `gh api` failure means the target comment is already absent
96
- * (HTTP 404 / 410) — the benign "a peer got there first" case. Auth failures,
97
- * rate limits (403), and every other error return false so they propagate and
98
- * surface a diagnostic instead of silently breaking the exactly-one invariant.
99
- */
100
- function isAlreadyGone(error) {
101
- const text = `${error && error.stderr ? error.stderr : ''} ${error && error.message ? error.message : ''}`;
102
- return /HTTP 404|HTTP 410|\bNot Found\b|\bGone\b/i.test(text);
103
- }
104
-
105
- /**
106
- * GitHub-backed client (shells to `gh api`, like lib/pr-monitor/gather.js). The
107
- * reconcile logic above is unit-tested; the error-tolerance in update/remove is
108
- * too, via an injectable `run`. create/update both send the same pre-rendered
109
- * payload file the render step wrote, so the body is identical.
110
- */
111
- function ghStickyClient({ repo, pr, payloadFile, run = runGh }) {
112
- return {
113
- async list() {
114
- const out = run(['api', `repos/${repo}/issues/${pr}/comments`, '--paginate']);
115
- return out && out.trim() ? JSON.parse(out) : [];
116
- },
117
- async create() {
118
- run(['api', '-X', 'POST', `repos/${repo}/issues/${pr}/comments`, '--input', payloadFile]);
119
- },
120
- async update(id) {
121
- try {
122
- run(['api', '-X', 'PATCH', `repos/${repo}/issues/comments/${id}`, '--input', payloadFile]);
123
- } catch (error) {
124
- if (!isAlreadyGone(error)) {
125
- throw error;
126
- }
127
- // Our chosen survivor was deleted by a peer whose survivor had a lower id:
128
- // the peer's sticky wins, exactly-one still holds, so we are done.
129
- }
130
- },
131
- async remove(id) {
132
- try {
133
- run(['api', '-X', 'DELETE', `repos/${repo}/issues/comments/${id}`]);
134
- } catch (error) {
135
- if (!isAlreadyGone(error)) {
136
- throw error;
137
- }
138
- // Already gone (a concurrent run deleted it first) — the goal state holds.
139
- }
140
- },
141
- };
142
- }
143
-
144
- async function main() {
145
- const repo = process.env.GH_REPO;
146
- const pr = process.env.PR;
147
- const payloadFile = process.env.STICKY_PAYLOAD_FILE || 'monitor-payload.json';
148
- const { STICKY_MARKER } = require('./render-sticky');
149
-
150
- const client = ghStickyClient({ repo, pr, payloadFile });
151
- const { survivor, deleted } = await upsertStickyComment({ marker: STICKY_MARKER }, client);
152
- const survivorLabel = survivor === null ? 'created (not yet visible)' : survivor;
153
- console.log(`Sticky comment reconciled to one: survivor=${survivorLabel}, deleted=${deleted.length}`);
154
- }
155
-
156
- if (require.main === module) {
157
- main().catch((error) => {
158
- console.error(error.message);
159
- process.exit(1);
160
- });
161
- }
162
-
163
- module.exports = {
164
- upsertStickyComment,
165
- markerCommentIds,
166
- sortIdsAscending,
167
- isAlreadyGone,
168
- ghStickyClient,
169
- };