crewforth 3.1.0-rc.5 → 3.1.0-rc.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 (57) hide show
  1. package/CHANGELOG.md +76 -12
  2. package/README.md +3 -2
  3. package/kit/AGENT_TEMPLATE.md +41 -21
  4. package/kit/CLAUDE.md +9 -7
  5. package/kit/agents/crew-backend-expert.md +4 -1
  6. package/kit/agents/crew-commit-agent.md +8 -3
  7. package/kit/agents/crew-database-expert.md +4 -1
  8. package/kit/agents/crew-devops-expert.md +4 -1
  9. package/kit/agents/crew-frontend-expert.md +4 -1
  10. package/kit/agents/crew-performance-expert.md +4 -1
  11. package/kit/agents/crew-planner.md +10 -1
  12. package/kit/agents/crew-privacy-agent.md +4 -1
  13. package/kit/agents/crew-review-agent.md +4 -1
  14. package/kit/agents/crew-security-expert.md +4 -1
  15. package/kit/agents/crew-session-manager.md +4 -1
  16. package/kit/agents/crew-test-expert.md +4 -1
  17. package/kit/eval/smoke-test.sh +860 -27
  18. package/kit/hooks/agent-outcome.sh +437 -0
  19. package/kit/hooks/context-usage.sh +1 -1
  20. package/kit/hooks/guard-agent-model.sh +494 -60
  21. package/kit/hooks/guard-bash.sh +69 -8
  22. package/kit/hooks/guard-schedule.sh +8 -2
  23. package/kit/hooks/guard-write.sh +31 -0
  24. package/kit/hooks/prompt-approval.sh +94 -7
  25. package/kit/settings.json +43 -0
  26. package/kit/skills/crew-approve/SKILL.md +4 -1
  27. package/kit/skills/crew-loosen/SKILL.md +28 -0
  28. package/kit/studio/server/hooks/studio-gate.sh +90 -4
  29. package/kit/studio/server/hooks/studio-host.mjs +92 -0
  30. package/kit/studio/server/index.js +85 -6
  31. package/kit/studio/server/lib/agent-origin.js +44 -0
  32. package/kit/studio/server/lib/graph.js +45 -7
  33. package/kit/studio/server/lib/model-outcomes.js +287 -0
  34. package/kit/studio/server/lib/palette.js +5 -18
  35. package/kit/studio/server/lib/permissions.js +260 -17
  36. package/kit/studio/server/lib/session.js +41 -1
  37. package/kit/studio/server/lib/usage.js +8 -1
  38. package/kit/studio/web/app.js +203 -32
  39. package/kit/studio/web/approvals.js +53 -2
  40. package/kit/studio/web/ask.js +137 -0
  41. package/kit/studio/web/canvas.js +91 -42
  42. package/kit/studio/web/chat.js +11 -1
  43. package/kit/studio/web/convo.js +9 -0
  44. package/kit/studio/web/dock.js +30 -9
  45. package/kit/studio/web/graph-plan.js +29 -9
  46. package/kit/studio/web/icons/builtin.svg +1 -1
  47. package/kit/studio/web/index.html +11 -4
  48. package/kit/studio/web/keys.js +29 -0
  49. package/kit/studio/web/list-plan.js +8 -3
  50. package/kit/studio/web/list.js +27 -5
  51. package/kit/studio/web/models-plan.js +167 -0
  52. package/kit/studio/web/models.js +127 -0
  53. package/kit/studio/web/names.js +30 -0
  54. package/kit/studio/web/style.css +85 -9
  55. package/kit/studio/web/timeline-plan.js +8 -0
  56. package/kit/studio/web/timeline.js +26 -7
  57. package/package.json +1 -1
@@ -3,7 +3,7 @@
3
3
  // Which rows there are, where a bar starts and ends and what it says is decided in timeline-plan.js. This file
4
4
  // draws that, and hands back what the viewer did: a row chosen, a group folded, the view moved.
5
5
  import {
6
- rows, groupsOf, extent, windowOf, place, ticks, clock, stepRange, waitNote, factsOf, xOf, RANGES, DEFAULT_RANGE,
6
+ rows, groupsOf, foldedSet, extent, windowOf, place, ticks, clock, stepRange, waitNote, factsOf, xOf, RANGES, DEFAULT_RANGE,
7
7
  } from './timeline-plan.js';
8
8
  import { Press } from './press.js';
9
9
  import { shownType, hoverOf, modelOf, modelFamily } from './names.js';
@@ -39,7 +39,8 @@ export class Timeline {
39
39
  this.range = DEFAULT_RANGE;
40
40
  this.follow = true;
41
41
  this.end = null; // where the view ends when it is not following
42
- this.folded = new Set();
42
+ this.fold = new Map(); // group id -> folded, the viewer's own choices
43
+ this.all = null; // true after Expand all, false after Fold all: what an untouched group is
43
44
  this.expanded = new Set();
44
45
  this.waiting = new Set();
45
46
  this.filter = null; // Set of statuses, or null
@@ -103,7 +104,8 @@ export class Timeline {
103
104
  if (id === this.session) return;
104
105
  this.session = id;
105
106
  this.nodes = [];
106
- this.folded = new Set();
107
+ this.fold = new Map(); // group id -> folded, the viewer's own choices
108
+ this.all = null; // true after Expand all, false after Fold all: what an untouched group is
107
109
  this.expanded = new Set();
108
110
  this.selected = null;
109
111
  this.follow = true;
@@ -113,7 +115,21 @@ export class Timeline {
113
115
  }
114
116
 
115
117
  setNodes(nodes) { this.nodes = nodes ?? []; this.render(); }
116
- setGroup(g) { if (g === this.group) return; this.group = g; this.folded = new Set(); this.expanded = new Set(); this.render(); }
118
+ setGroup(g) { if (g === this.group) return; this.group = g; this.fold = new Map(); this.all = null; this.expanded = new Set(); this.render(); }
119
+
120
+ /** Open every group and show every row of the long ones; a group that arrives later arrives open. */
121
+ expandAll() { this.all = true; this.fold = new Map(); this.render(); this.#changed(); }
122
+ /** Fold every group; one that arrives later arrives folded. */
123
+ foldAll() { this.all = false; this.fold = new Map(); this.expanded = new Set(); this.render(); this.#changed(); }
124
+ /** Whether there is anything left for each of the two to do. */
125
+ foldState() {
126
+ const heads = (this.lastRows ?? []).filter((r) => r.kind === 'group');
127
+ return {
128
+ groups: heads.length,
129
+ canExpand: heads.some((r) => r.folded) || (this.lastRows ?? []).some((r) => r.kind === 'more'),
130
+ canFold: heads.some((r) => !r.folded),
131
+ };
132
+ }
117
133
  setWaiting(ids) { this.waiting = new Set(ids ?? []); this.render(); }
118
134
  setFilter(statuses, { keepWaiting = false } = {}) { this.filter = statuses ? new Set(statuses) : null; this.keepWaiting = keepWaiting; this.render(); }
119
135
  setModelFilter(family) { this.modelFilter = family ?? null; this.render(); }
@@ -151,7 +167,7 @@ export class Timeline {
151
167
  if (id) {
152
168
  for (const g of groupsOf(this.nodes, this.group)) {
153
169
  if (!g.members.some((m) => m.id === id)) continue;
154
- this.folded.delete(g.id);
170
+ this.fold.set(g.id, false);
155
171
  this.expanded.add(g.id);
156
172
  }
157
173
  }
@@ -212,10 +228,12 @@ export class Timeline {
212
228
  // The rows are drawn again from the data each time; a keyboard reader's place among them is kept.
213
229
  const focused = this.rowsEl.contains?.(document.activeElement) ? document.activeElement.dataset?.key ?? null : null;
214
230
  const top = this.body.scrollTop;
231
+ const ids = groupsOf(this.nodes, this.group).map((g) => g.id);
215
232
  const list = rows(this.nodes, {
216
- group: this.group, folded: this.folded, expanded: this.expanded, waiting: this.waiting,
233
+ group: this.group, folded: foldedSet(ids, this.fold, this.all), expanded: this.all === true ? new Set(ids) : this.expanded, waiting: this.waiting,
217
234
  approvals: this.owned?.approvals ?? null, now,
218
235
  });
236
+ this.lastRows = list;
219
237
  this.rowsEl.replaceChildren(...list.map((r) => this.#row(r, win, width)));
220
238
  this.body.scrollTop = top;
221
239
  if (focused) [...this.rowsEl.querySelectorAll('[data-key]')].find((el) => el.dataset.key === focused)?.focus();
@@ -305,8 +323,9 @@ export class Timeline {
305
323
  chev.innerHTML = '<svg class="ic" viewBox="0 0 16 16" aria-hidden="true"><path d="M6 4l4 4-4 4"/></svg>';
306
324
  lab.append(chev, mk('span', 'tl-name', r.label), mk('span', 'row-fill'), mk('span', 'sub', r.sub));
307
325
  lab.addEventListener('click', () => {
308
- if (this.folded.has(r.id)) this.folded.delete(r.id); else this.folded.add(r.id);
326
+ this.fold.set(r.id, !r.folded);
309
327
  this.render();
328
+ this.#changed();
310
329
  });
311
330
  this.#bars(track, r.lanes, win, width);
312
331
  } else if (r.kind === 'merged') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "crewforth",
3
- "version": "3.1.0-rc.5",
3
+ "version": "3.1.0-rc.7",
4
4
  "description": "Crewforth — your engineering crew for Claude Code: agents, skills and tool-level gates, in any stack.",
5
5
  "bin": {
6
6
  "crewforth": "bin/cli.js"