gcf-board 0.2.17 → 0.2.19
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/README.md
CHANGED
|
@@ -52,6 +52,7 @@ It puts **GCF Board** on the desktop (Windows: a shortcut whose server console s
|
|
|
52
52
|
- **Lessons** (core 0.7.10, harness 0.9.2): what got in an agent's way, asked at the end of each session, lands in the inbox with its kind (`tool`, `rules`, `framework`, `template`, `other`) and its item. The inbox filters them; a card shows `N lessons`, and the drawer lists the item's lessons with the inbox's buttons. A run's report line ends with `, N lessons`, and with `— code written outside aif-implement` when a session skipped that skill.
|
|
53
53
|
- **Playtest**: `gcf playtest session` with the checklist grouped by subsystem; keep, tune or kill each item from the list. **Build** runs `dod.build`. A note goes to the inbox as `--source playtest`.
|
|
54
54
|
- **Ended**: history (accepted) and graveyard (killed, dropped, won't-fix).
|
|
55
|
+
- **Metrics**: `gcf-harness metrics --json` of the project — how many runs reached submit or close, what they cost, handoffs, plan checks, DoD attempts, guard firings, block reasons — and, with harness 0.9.5, the cost per item over all its runs and per week and day.
|
|
55
56
|
|
|
56
57
|
The page updates by itself: the server watches each project's `gcf/` and `.gcf-session/runs/`.
|
|
57
58
|
|
package/dist/server/app.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// The board's HTTP server: the page, and an API that is a thin layer over `ai-factory gcf`.
|
|
2
|
-
// Reads: projects, the board, a shown file, a run log, the playtest session
|
|
2
|
+
// Reads: projects, the board, a shown file, a run log, the playtest session, the run metrics
|
|
3
|
+
// (`ai-factory gcf-harness metrics`). Writes: a project
|
|
3
4
|
// on the board, a move, a run. Every write is a command of the framework.
|
|
4
5
|
import { randomBytes } from 'node:crypto';
|
|
5
6
|
import { createServer } from 'node:http';
|
|
@@ -143,7 +144,7 @@ export function createApp(o) {
|
|
|
143
144
|
const p = await projectOf(m[1]);
|
|
144
145
|
// The board's own runs ride along: what it started and what waits in its queue; and the
|
|
145
146
|
// count of plan writes, which tells the page an open plan's text changed.
|
|
146
|
-
const own = { runs: runsOf(p.name), plans_rev: planRev(p.name) };
|
|
147
|
+
const own = { runs: runsOf(p.name), plans_rev: planRev(p.name), board_version: o.version };
|
|
147
148
|
const board = await boardOf(p);
|
|
148
149
|
// A running item's last log write tells a working session from a stuck one; a run's
|
|
149
150
|
// lock is this board's run or a console's.
|
|
@@ -185,6 +186,14 @@ export function createApp(o) {
|
|
|
185
186
|
send(res, 200, jsonOf(r, 'gcf playtest session'));
|
|
186
187
|
},
|
|
187
188
|
],
|
|
189
|
+
[
|
|
190
|
+
'GET',
|
|
191
|
+
/^\/api\/p\/([^/]+)\/metrics$/,
|
|
192
|
+
async (_req, res, m) => {
|
|
193
|
+
const r = await cli(await projectOf(m[1]), ['gcf-harness', 'metrics', '--json']);
|
|
194
|
+
send(res, 200, jsonOf(r, 'gcf-harness metrics'));
|
|
195
|
+
},
|
|
196
|
+
],
|
|
188
197
|
[
|
|
189
198
|
// A build runs the project's own command: a write, so a POST with its Origin checked.
|
|
190
199
|
'POST',
|
package/dist/server/cli.js
CHANGED
|
@@ -43,11 +43,13 @@ function globalRoots() {
|
|
|
43
43
|
return roots;
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
|
-
* The
|
|
47
|
-
*
|
|
46
|
+
* The refusal an operator reads: the core throws, and node prints the stack under an
|
|
47
|
+
* `Error: <message>` header, every line of the message (a git failure's cause comes after its
|
|
48
|
+
* first line). Anything else is shown as it came.
|
|
48
49
|
*/
|
|
49
50
|
export function briefError(err) {
|
|
50
|
-
|
|
51
|
+
const m = err.match(/^\w*Error: ([\s\S]*?)\r?\n {4}at /m) ?? err.match(/^\w*Error: (.+)$/m);
|
|
52
|
+
return (m?.[1] ?? err).replace(/\r/g, '').trim();
|
|
51
53
|
}
|
|
52
54
|
/** Run `ai-factory <args>` in a project; a move can wait up to a minute on gcf's main lock. */
|
|
53
55
|
export function runCli(entry, cwd, args, timeoutMs = 120_000) {
|
package/dist/server/main.js
CHANGED
|
@@ -68,7 +68,13 @@ async function main() {
|
|
|
68
68
|
const token = await boardToken();
|
|
69
69
|
const url = `http://127.0.0.1:${port}/?t=${token}`;
|
|
70
70
|
const open = () => !values['no-open'] && openInBrowser(url);
|
|
71
|
-
const app = createApp({
|
|
71
|
+
const app = createApp({
|
|
72
|
+
entry: aiFactoryEntry(),
|
|
73
|
+
token,
|
|
74
|
+
port,
|
|
75
|
+
web: webDir(),
|
|
76
|
+
version: version(),
|
|
77
|
+
});
|
|
72
78
|
app.on('error', async (e) => {
|
|
73
79
|
if (e.code !== 'EADDRINUSE') {
|
|
74
80
|
console.error(`gcf-board: cannot listen on port ${port}: ${e.message}`);
|
package/dist/server/moves.js
CHANGED
|
@@ -41,6 +41,19 @@ const many = (b, key) => {
|
|
|
41
41
|
throw new HttpError(400, `${key} must be a list of text`);
|
|
42
42
|
return v.filter(Boolean).map((x) => `--${key}=${x}`);
|
|
43
43
|
};
|
|
44
|
+
// The core limits a tag to 40 characters too.
|
|
45
|
+
const TAG = /^[a-z0-9][a-z0-9:-]{0,39}$/;
|
|
46
|
+
/** A list as one comma-separated `--flag=` (a repeated option keeps only its last). */
|
|
47
|
+
const list = (b, key, re, what, flag = key) => {
|
|
48
|
+
const v = b[key];
|
|
49
|
+
if (v === undefined)
|
|
50
|
+
return [];
|
|
51
|
+
if (!Array.isArray(v) || v.some((x) => typeof x !== 'string' || !re.test(x)))
|
|
52
|
+
throw new HttpError(400, `${key} must be a list of ${what}`);
|
|
53
|
+
return v.length ? [`--${flag}=${v.join(',')}`] : [];
|
|
54
|
+
};
|
|
55
|
+
const idList = (b, key) => list(b, key, SLUG, 'ids');
|
|
56
|
+
const tagList = (b, key, flag = key) => list(b, key, TAG, 'tags', flag);
|
|
44
57
|
/** `--remove` / `--no-remove` when the operator chose; the command's default otherwise. */
|
|
45
58
|
const remove = (b) => b.remove === true ? ['--remove'] : b.remove === false ? ['--no-remove'] : [];
|
|
46
59
|
const NEW_ITEM_OPTIONS = [
|
|
@@ -102,13 +115,24 @@ const MOVES = {
|
|
|
102
115
|
'inbox',
|
|
103
116
|
'add',
|
|
104
117
|
...opt(b, 'source'),
|
|
118
|
+
...tagList(b, 'tags'),
|
|
105
119
|
'--commit',
|
|
106
120
|
`--text=${required(b, 'text')}`,
|
|
107
121
|
],
|
|
108
122
|
'inbox-drop': (b) => ['inbox', 'drop', id(b), '--commit'],
|
|
123
|
+
'inbox-tag': (b) => [
|
|
124
|
+
'inbox',
|
|
125
|
+
'tag',
|
|
126
|
+
id(b),
|
|
127
|
+
...tagList(b, 'add'),
|
|
128
|
+
...tagList(b, 'remove'),
|
|
129
|
+
'--commit',
|
|
130
|
+
],
|
|
109
131
|
'phase-next': (b) => ['phase', 'next', ...opt(b, 'reason')],
|
|
110
132
|
// A column's new order after a drag: ranks 1, 2, 3… in one commit.
|
|
111
133
|
prioritize: (b) => ['prioritize', ...runIds(b)],
|
|
134
|
+
// The ids an item waits for, added and removed; the core refuses an unknown id and a cycle.
|
|
135
|
+
depends: (b) => ['depends', id(b), ...idList(b, 'add'), ...idList(b, 'remove')],
|
|
112
136
|
'question-answer': (b) => ['question', 'answer', id(b), `--text=${required(b, 'text')}`],
|
|
113
137
|
// One setting of gcf/config.yml; the core checks the key and the value. An empty value takes
|
|
114
138
|
// a number back to its default.
|