@volter/supercode-orchestrator 0.4.153 → 0.4.154

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.
package/board/ci.mjs CHANGED
@@ -126,6 +126,20 @@ function registeredBranches(board, id) {
126
126
  return out;
127
127
  }
128
128
 
129
+ /** Whether `head` is a branch named for card `id`: `wt/<id>` (or `wt/t-…`), alone or with a `-…`/`/…` suffix. */
130
+ export const namedWorkBranch = (id, head) => [`wt/${id}`, `wt/${id.replace(/^t_/, 't-')}`].some((prefix) => head === prefix || head.startsWith(`${prefix}-`) || head.startsWith(`${prefix}/`));
131
+
132
+ /** The branch names an arc declares as its work: its own and its subtasks' `--branch`, and each branch its latest
133
+ * review request covered. With `namedWorkBranch`, these are the branches its PRs are opened from. */
134
+ export function declaredWorkBranches(db, id) {
135
+ const declared = db.prepare(`SELECT t.branch_name FROM tasks t LEFT JOIN supercode_cards c ON c.task_id=t.id
136
+ WHERE (t.id=? OR c.subtask_of=?) AND t.status != 'cancelled' AND t.branch_name IS NOT NULL`).all(id, id).map((r) => r.branch_name);
137
+ const row = db.prepare("SELECT payload FROM task_events WHERE task_id=? AND kind='review_requested' ORDER BY id DESC LIMIT 1").get(id);
138
+ let reviewed = [];
139
+ try { reviewed = candidatePullRequests(row && JSON.parse(row.payload)?.ci).map((p) => p.branch); } catch { /* an unreadable payload names none */ }
140
+ return [...new Set([...declared, ...reviewed].filter(Boolean))];
141
+ }
142
+
129
143
  /** Every opened PR of the arc, each proposed to its repository's default branch: an open PR whose head is the card's
130
144
  * work branch in the card's own repository, a `wt/<card id>` branch in any repository this machine keeps a checkout
131
145
  * of, or a sibling checkout the arc registered (`registeredBranches`). The repository's default branch, declared or
@@ -140,8 +154,7 @@ export function openReviewPullRequests(board, id) {
140
154
  const current = task?.branch_name ? null : run('git', ['branch', '--show-current'], own);
141
155
  let branch = task?.branch_name ?? (current || null);
142
156
  const registered = registeredBranches(board, id);
143
- const named = (head) => [`wt/${id}`, `wt/${id.replace(/^t_/, 't-')}`].some((prefix) => head === prefix || head.startsWith(`${prefix}-`) || head.startsWith(`${prefix}/`));
144
- const mine = (repository, head) => (repository === ownRepository && head === branch) || named(head)
157
+ const mine = (repository, head) => (repository === ownRepository && head === branch) || namedWorkBranch(id, head)
145
158
  || registered.some((r) => r.repository === repository && r.branch === head);
146
159
  const prs = [];
147
160
  for (const repository of new Set([ownRepository, ...registered.map((r) => r.repository), ...knownRepositories()])) {
@@ -11,7 +11,7 @@ import { resolve, relative, isAbsolute } from 'node:path';
11
11
  import { randomBytes } from 'node:crypto';
12
12
  import { actorOf, appendRunObservation, event as record, json, now } from './store.mjs';
13
13
  import { evaluate, holds, render } from './expr.mjs';
14
- import { completionEvidence, reviewCandidate } from './ci.mjs';
14
+ import { completionEvidence, declaredWorkBranches, namedWorkBranch, reviewCandidate } from './ci.mjs';
15
15
  import { isWorkspacePath } from './workspace.mjs';
16
16
  import { identifyNotice } from './notices.mjs';
17
17
 
@@ -103,17 +103,29 @@ function pattern(p) {
103
103
  return text.startsWith('(?i)') ? new RegExp(text.slice(4), 'i') : new RegExp(text);
104
104
  }
105
105
 
106
- // the time of the latest commit that names `id` in its message, on any ref of the repository at `path` (a worktree's
107
- // pushes land on the shared remote-tracking refs), or null (asked only when an expression does). Merge commits are
108
- // not the card's work: merging its reviewed PR, or main into its branch, is not a newer change to review.
106
+ // the time of the latest commit that names `id` in its message on the card's work in the repository at `path`, or null
107
+ // (asked only when an expression does). Its work is the branches its PRs are opened from (the checkout's own branch,
108
+ // a declared or reviewed one, `wt/<id>…`), local or remote-tracking (a worktree's pushes land on the shared refs),
109
+ // and the default branch they merge into. Another ref naming the card (an evidence branch, a later card's work) is not
110
+ // a change to its reviewed work. Merge commits are not the card's work: merging its reviewed PR, or main into its
111
+ // branch, is not a newer change to review.
109
112
  const commitTimes = new Map();
110
- function observeLastCommit(path, id) {
113
+ function observeLastCommit(path, id, branches = []) {
111
114
  if (!path) return null;
112
115
  const key = `${path}:${id}`;
113
116
  const cached = commitTimes.get(key);
114
117
  if (cached && now() - cached.at < 15) return cached.value;
118
+ const git = (repo, args) => spawnSync('git', ['-C', repo, ...args], { encoding: 'utf8', timeout: 10_000 });
115
119
  const readCommit = (repo) => {
116
- const r = spawnSync('git', ['-C', repo, 'log', '--all', '--no-merges', '-1', '--format=%ct', '--fixed-strings', `--grep=${id}`], { encoding: 'utf8', timeout: 10_000 });
120
+ const listed = git(repo, ['for-each-ref', '--format=%(refname)', 'refs/heads', 'refs/remotes']);
121
+ if (listed.status !== 0) return { repo: false, value: null };
122
+ const current = git(repo, ['branch', '--show-current']).stdout?.trim();
123
+ const fallback = git(repo, ['symbolic-ref', '--quiet', 'refs/remotes/origin/HEAD']).stdout?.trim().replace(/^refs\/remotes\/origin\//, '');
124
+ const names = new Set([...branches, current, fallback].filter(Boolean));
125
+ const short = (ref) => ref.startsWith('refs/heads/') ? ref.slice(11) : ref.split('/').slice(3).join('/');
126
+ const refs = listed.stdout.split('\n').filter((ref) => ref && !ref.endsWith('/HEAD') && (names.has(short(ref)) || namedWorkBranch(id, short(ref))));
127
+ if (!refs.length) return { repo: true, value: null };
128
+ const r = git(repo, ['log', ...refs, '--no-merges', '-1', '--format=%ct', '--fixed-strings', `--grep=${id}`, '--']);
117
129
  const value = Number(r.stdout?.trim());
118
130
  return { repo: r.status === 0, value: Number.isFinite(value) && value > 0 ? value : null };
119
131
  };
@@ -145,7 +157,7 @@ export function refreshCardInputs(board, id, { commits = false } = {}) {
145
157
  const parent=!own && task.subtask_of?board.db.prepare('SELECT workspace_path FROM tasks WHERE id=?').get(task.subtask_of)?.workspace_path:null;
146
158
  const path=own??parent??null;
147
159
  if(path)commitTimes.delete(`${path}:${id}`);
148
- observeLastCommit(path,id);
160
+ observeLastCommit(path,id,declaredWorkBranches(board.db,id));
149
161
  }
150
162
  }
151
163
 
@@ -307,7 +319,8 @@ export function scopeOf(db, board, task, { params, event = {}, extra = {} } = {}
307
319
  last_events: Object.fromEntries(all(db, 'SELECT e.kind, e.created_at, e.payload FROM task_events e JOIN (SELECT kind, MAX(id) AS id FROM task_events WHERE task_id = ? GROUP BY kind) l ON l.id = e.id', task.id)
308
320
  .map((r) => [r.kind, { created_at: r.created_at, payload: json(r.payload, {}) ?? {} }])),
309
321
  comments_since: (t) => all(db, 'SELECT author, body, created_at FROM task_comments WHERE task_id = ? AND created_at >= ?', task.id, t),
310
- // the latest commit naming this card, in its workspace's repository (a subtask's: its arc's), as epoch seconds
322
+ // the latest commit naming this card on its work branches and default branch, in its workspace's repository (a
323
+ // subtask's: its arc's), as epoch seconds
311
324
  last_commit_at: () => {
312
325
  const own = task.workspace_kind !== 'scratch' ? task.workspace_path : null;
313
326
  const arcWs = !own && task.subtask_of ? one(db, "SELECT workspace_path FROM tasks WHERE id = ? AND workspace_kind != 'scratch'", task.subtask_of)?.workspace_path : null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-orchestrator",
3
- "version": "0.4.153",
3
+ "version": "0.4.154",
4
4
  "type": "module",
5
5
  "description": "The orchestrator runtime over the Volter Harness ontology: one typed operational model whose folder is its serialization, read and written through the harness orchestration doors (docs/ORCHESTRATOR-IR.md)",
6
6
  "exports": {