@bongos/core 1.19.322 → 1.19.324
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/.bongos-core.json +59 -34
- package/clients/bongos-client/index.d.ts +1 -1
- package/docs/api/openapi.json +5 -1
- package/docs/api-reference.md +2 -2
- package/docs/copy-inventory.md +17 -16
- package/docs/copy-registry.json +30 -21
- package/migrations/core_224_goal_subtitle.sql +35 -0
- package/modules/hall-ui/public/goals-lib.js +26 -0
- package/modules/hall-ui/public/goals-page.js +5 -5
- package/modules/hall-ui/public/goals.css +68 -0
- package/modules/hall-ui/public/goals.html +9 -1
- package/modules/lifecycle/db-goals.js +5 -5
- package/modules/lifecycle/goal-edits.js +11 -5
- package/modules/lifecycle/routes/goals.js +3 -3
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/architect-audit.js +7 -1
- package/scripts/gds/audit-deps.js +11 -1
- package/scripts/gds/capture.js +4 -16
- package/scripts/gds/fitness.js +1 -1
- package/scripts/gds/session-eval.js +6 -1
- package/scripts/gds/ship-flow.js +4 -0
- package/scripts/gds/smoke-batch-claim.sh +9 -1
- package/scripts/gds/smoke-batch-release.sh +9 -1
- package/scripts/gds/smoke-claim-consistency.sh +10 -1
- package/scripts/gds/smoke-write.sh +10 -1
- package/scripts/gds/triage.js +4 -1
- package/scripts/gds/version-literal-guard.js +122 -0
- package/scripts/gds/version-select.js +79 -0
- package/src/module-api.js +5 -1
- package/tests/goal_subtitle.mjs +237 -0
- package/tests/goal_tier.mjs +6 -4
- package/tests/version_literals.mjs +201 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
-- core_224_goal_subtitle.sql — a goal's explanation stops living inside its
|
|
2
|
+
-- name (task 1003224, goal 1000072, criterion legibility-goal-names-and-claude-text).
|
|
3
|
+
--
|
|
4
|
+
-- THE PROBLEM THIS FIXES IS IN THE DATA. 29 of 38 open goals carry their own
|
|
5
|
+
-- explanation appended to `title` after a dash — "Findability — the grep path",
|
|
6
|
+
-- "Idea routing — work that has a home lands in it". The goal board is the
|
|
7
|
+
-- primary work surface, so every list, card, pill and breadcrumb renders the
|
|
8
|
+
-- explanation at the same weight as the name, and nothing can show one without
|
|
9
|
+
-- the other. A name and a description are two fields; they were one.
|
|
10
|
+
--
|
|
11
|
+
-- ADDITIVE ONLY, and nullable with no default on purpose. Every existing row
|
|
12
|
+
-- keeps its title byte-for-byte: this migration creates somewhere for the
|
|
13
|
+
-- explanation to GO, and the separate renaming pass (task 1003225) moves it,
|
|
14
|
+
-- goal by goal, with judgment — three goals all head "Agents module" and would
|
|
15
|
+
-- become indistinguishable under a mechanical split. A DEFAULT '' would make
|
|
16
|
+
-- "never set" and "deliberately blank" the same state, and the UI needs to tell
|
|
17
|
+
-- them apart to know whether to render the affordance at all.
|
|
18
|
+
--
|
|
19
|
+
-- WHY THIS IS A CORE MIGRATION AND NOT A MODULE ONE. `goals` was created by
|
|
20
|
+
-- repo-root migrations/160_goal_tier.sql and is a CORE table — the lifecycle
|
|
21
|
+
-- module owns the queries against it, not the table itself (modules/lifecycle/
|
|
22
|
+
-- CLAUDE.md: "None owned"). Fitness check 11 enforces exactly that: a module
|
|
23
|
+
-- migration may only touch its own `<key>_`-prefixed tables, so an
|
|
24
|
+
-- `ALTER TABLE goals` from modules/lifecycle/migrations/ is refused however
|
|
25
|
+
-- lifecycle-ish it feels (ADR 0083 §Decision #5). The first draft of this file
|
|
26
|
+
-- lived there and the check caught it.
|
|
27
|
+
|
|
28
|
+
ALTER TABLE goals
|
|
29
|
+
ADD COLUMN IF NOT EXISTS subtitle text;
|
|
30
|
+
|
|
31
|
+
COMMENT ON COLUMN goals.subtitle IS
|
|
32
|
+
'The goal''s explanation, held apart from its name. Rendered subordinate to title and on demand (task 1003224). NULL = never set, which is distinct from an empty string meaning deliberately blank.';
|
|
33
|
+
|
|
34
|
+
INSERT INTO schema_migrations (version) VALUES ('core_224_goal_subtitle')
|
|
35
|
+
ON CONFLICT (version) DO NOTHING;
|
|
@@ -90,10 +90,36 @@
|
|
|
90
90
|
const msOf = (iso) => { const t = iso ? Date.parse(iso) : NaN; return Number.isNaN(t) ? 0 : t; };
|
|
91
91
|
const taskActivityMs = (t) => Math.max(msOf(t.updated_at), msOf(t.shipped_at));
|
|
92
92
|
|
|
93
|
+
// task 1003224 — on a LIST, a goal's subtitle is a TOOLTIP, never a second
|
|
94
|
+
// visible line. The board's job there is to let someone find a goal by name;
|
|
95
|
+
// rendering every explanation inline is what led 29 of 38 titles to carry
|
|
96
|
+
// their own after a dash. Escaped because it lands inside an attribute.
|
|
97
|
+
function subtitleAttr(g) {
|
|
98
|
+
const t = (g && g.subtitle ? String(g.subtitle) : '').trim();
|
|
99
|
+
return t ? ` title="${window.OTB.escapeHtml(t)}"` : '';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// task 1003224 — the three-layer prose rules for a goal's detail head, kept
|
|
103
|
+
// here rather than in goals-page.js because that file is 500 lines over the
|
|
104
|
+
// excess-lines ratchet and this one exists for exactly that reason. It WRITES
|
|
105
|
+
// to elements the caller already looked up; it never reads the DOM or touches
|
|
106
|
+
// page state, so the file's purity contract holds.
|
|
107
|
+
// name -> the h2, rendered by the caller
|
|
108
|
+
// subtitle -> subordinate, and ABSENT when unset (NULL differs from blank)
|
|
109
|
+
// Claude's prose -> collapsed, and absent when there is nothing to reveal
|
|
110
|
+
function applyGoalProse(els, g) {
|
|
111
|
+
const subtitle = (g.subtitle || '').trim();
|
|
112
|
+
const description = g.description || '';
|
|
113
|
+
if (els.sub) { els.sub.textContent = subtitle; els.sub.hidden = !subtitle; }
|
|
114
|
+
if (els.desc) els.desc.textContent = description;
|
|
115
|
+
if (els.claude) { els.claude.hidden = !description.trim(); els.claude.open = false; }
|
|
116
|
+
}
|
|
117
|
+
|
|
93
118
|
window.OTBGoalsLib = {
|
|
94
119
|
DONE_STATUSES, CUT_STATUSES, isTodoStatus,
|
|
95
120
|
ring, memberName,
|
|
96
121
|
readinessOf, EMPTY_ROLLUP, goalRollLine,
|
|
97
122
|
matchesTodoSearch, msOf, taskActivityMs,
|
|
123
|
+
subtitleAttr, applyGoalProse,
|
|
98
124
|
};
|
|
99
125
|
})();
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
// so every call site below reads exactly as before.
|
|
41
41
|
const { DONE_STATUSES, CUT_STATUSES, isTodoStatus, ring, memberName,
|
|
42
42
|
readinessOf, EMPTY_ROLLUP, goalRollLine, matchesTodoSearch,
|
|
43
|
-
msOf, taskActivityMs } = window.OTBGoalsLib;
|
|
43
|
+
msOf, taskActivityMs, subtitleAttr, applyGoalProse } = window.OTBGoalsLib;
|
|
44
44
|
const API = '/api/bongos';
|
|
45
45
|
// Generated typed client (task 1996 / R09), served as window.BongosClient by
|
|
46
46
|
// /lib/bongos-client.js. baseUrl:'' passes the full same-origin paths this file
|
|
@@ -359,7 +359,7 @@
|
|
|
359
359
|
return `<div class="gr-row" data-goal-id="${escapeHtml(String(g.id))}">
|
|
360
360
|
<div class="gr-ringcol">${ringHtml}</div>
|
|
361
361
|
<div class="gr-main">
|
|
362
|
-
<a class="gr-title" href="${goalHref}">${escapeHtml(g.title || 'Untitled goal')}</a>
|
|
362
|
+
<a class="gr-title" href="${goalHref}"${subtitleAttr(g)}>${escapeHtml(g.title || 'Untitled goal')}</a>
|
|
363
363
|
<div class="gr-meta">${metaHtml}</div>
|
|
364
364
|
${achieved ? '' : goalRollLine(r)}
|
|
365
365
|
</div>
|
|
@@ -377,7 +377,7 @@
|
|
|
377
377
|
return `<div class="goal-card" data-goal-id="${escapeHtml(String(g.id))}">
|
|
378
378
|
<div class="goal-card__head">
|
|
379
379
|
<div class="gr-ringcol">${ringHtml}</div>
|
|
380
|
-
<a class="goal-card__title gr-title" href="${goalHref}">${escapeHtml(g.title || 'Untitled goal')}</a>
|
|
380
|
+
<a class="goal-card__title gr-title" href="${goalHref}"${subtitleAttr(g)}>${escapeHtml(g.title || 'Untitled goal')}</a>
|
|
381
381
|
</div>
|
|
382
382
|
<div class="goal-card__meta">${metaHtml}</div>
|
|
383
383
|
${achieved ? '' : goalRollLine(r)}
|
|
@@ -1211,8 +1211,8 @@
|
|
|
1211
1211
|
// task 2163 — surface the numeric goal id alongside the title (escaped title,
|
|
1212
1212
|
// so switching from textContent to innerHTML stays injection-safe).
|
|
1213
1213
|
if (h) h.innerHTML = `<span class="goal-detail__id" title="Goal id">#${escapeHtml(String(g.id))}</span> ${escapeHtml(g.title || 'Untitled goal')}`;
|
|
1214
|
-
|
|
1215
|
-
|
|
1214
|
+
// task 1003224 — name / subtitle / Claude's prose at three weights; the rules live in goals-lib.
|
|
1215
|
+
applyGoalProse({ sub: $('#goal-detail-subtitle'), desc: $('#goal-detail-desc'), claude: $('#goal-detail-claude') }, g);
|
|
1216
1216
|
const meta = $('#goal-detail-meta');
|
|
1217
1217
|
if (meta) {
|
|
1218
1218
|
const scope = Array.isArray(g.scope_modules) && g.scope_modules.length
|
|
@@ -259,3 +259,71 @@ a.goal-card:hover { text-decoration: none; border-color: var(--border-strong); b
|
|
|
259
259
|
.gr-next { align-items: flex-start; }
|
|
260
260
|
.gr-next__t { max-width: 100%; }
|
|
261
261
|
}
|
|
262
|
+
|
|
263
|
+
/* ── task 1003224: name, subtitle, and Claude's text at three weights ────────
|
|
264
|
+
The goal board is the primary work surface, so what it shows by DEFAULT is
|
|
265
|
+
the decision this makes. The name reads first; the explanation is visibly
|
|
266
|
+
subordinate; the bulk prose Claude writes is behind a disclosure and absent
|
|
267
|
+
from the default reading path until someone asks for it.
|
|
268
|
+
Tokens only from style.css's real set (--ink-soft, --ink-faint, --rule,
|
|
269
|
+
--surface, --radius-sm) — inventing one renders as nothing, which is how
|
|
270
|
+
copy-desk.css ended up with invisible fallbacks (idea 1000821). */
|
|
271
|
+
.goal-detail__subtitle {
|
|
272
|
+
color: var(--ink-soft);
|
|
273
|
+
font-size: 13px;
|
|
274
|
+
font-weight: 400;
|
|
275
|
+
line-height: 1.45;
|
|
276
|
+
max-width: 72ch;
|
|
277
|
+
margin: 3px 0 0;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/* The disclosure is deliberately quiet: a row someone can ignore. */
|
|
281
|
+
.claude-text {
|
|
282
|
+
margin: 10px 0 0;
|
|
283
|
+
max-width: 72ch;
|
|
284
|
+
border-top: 1px solid var(--rule);
|
|
285
|
+
padding-top: 8px;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.claude-text__toggle {
|
|
289
|
+
color: var(--ink-faint);
|
|
290
|
+
font-size: 12px;
|
|
291
|
+
cursor: pointer;
|
|
292
|
+
list-style: none;
|
|
293
|
+
user-select: none;
|
|
294
|
+
display: inline-flex;
|
|
295
|
+
align-items: center;
|
|
296
|
+
gap: 6px;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/* Own marker, so the affordance reads the same in every browser. */
|
|
300
|
+
.claude-text__toggle::-webkit-details-marker { display: none; }
|
|
301
|
+
.claude-text__toggle::before {
|
|
302
|
+
content: '▸';
|
|
303
|
+
font-size: 10px;
|
|
304
|
+
transition: transform 120ms ease;
|
|
305
|
+
}
|
|
306
|
+
.claude-text[open] > .claude-text__toggle::before { transform: rotate(90deg); }
|
|
307
|
+
.claude-text__toggle:hover { color: var(--ink-soft); }
|
|
308
|
+
.claude-text__toggle:focus-visible {
|
|
309
|
+
outline: 2px solid var(--accent);
|
|
310
|
+
outline-offset: 2px;
|
|
311
|
+
border-radius: var(--radius-sm);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/* Set apart once opened, so it reads as a machine's notes rather than as the
|
|
315
|
+
goal's own definition. */
|
|
316
|
+
.claude-text__body {
|
|
317
|
+
margin: 8px 0 0;
|
|
318
|
+
padding: 8px 10px;
|
|
319
|
+
background: var(--surface);
|
|
320
|
+
border-radius: var(--radius-sm);
|
|
321
|
+
color: var(--ink-soft);
|
|
322
|
+
font-size: 13px;
|
|
323
|
+
line-height: 1.5;
|
|
324
|
+
white-space: pre-wrap;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
@media (prefers-reduced-motion: reduce) {
|
|
328
|
+
.claude-text__toggle::before { transition: none; }
|
|
329
|
+
}
|
|
@@ -58,7 +58,15 @@
|
|
|
58
58
|
<div class="goal-detail__head">
|
|
59
59
|
<div style="flex:1;min-width:260px">
|
|
60
60
|
<h2 class="goal-detail__title" id="goal-detail-h">…</h2>
|
|
61
|
-
|
|
61
|
+
<!-- task 1003224: the goal's explanation, subordinate to its name. -->
|
|
62
|
+
<p class="goal-detail__subtitle" id="goal-detail-subtitle" hidden></p>
|
|
63
|
+
<!-- The bulk prose Claude writes. Collapsed by DEFAULT and marked as
|
|
64
|
+
Claude's text: the owner reads the name and the punched-in points,
|
|
65
|
+
and opens this only when they want the long form. -->
|
|
66
|
+
<details class="claude-text" id="goal-detail-claude" hidden>
|
|
67
|
+
<summary class="claude-text__toggle">Claude's notes on this goal</summary>
|
|
68
|
+
<p class="claude-text__body goal-detail__desc" id="goal-detail-desc"></p>
|
|
69
|
+
</details>
|
|
62
70
|
<p class="goal-card__meta" id="goal-detail-meta" style="margin-top:10px"></p>
|
|
63
71
|
</div>
|
|
64
72
|
<div class="goal-detail__actions" id="goal-detail-actions"></div>
|
|
@@ -32,7 +32,7 @@ const { deriveRequiredRank, highestRank } = require('./db-rank-authz.js');
|
|
|
32
32
|
// Shared column list so every goal read/return shapes a row identically.
|
|
33
33
|
|
|
34
34
|
const GOAL_COLS =
|
|
35
|
-
'id, version_id, title, description, scope_modules, status, visibility, created_by, succeeded_by_goal_id, sort_order, created_at, updated_at';
|
|
35
|
+
'id, version_id, title, subtitle, description, scope_modules, status, visibility, created_by, succeeded_by_goal_id, sort_order, created_at, updated_at';
|
|
36
36
|
|
|
37
37
|
// Shared column list for a goal_membership_requests row (task 1951 / ADR 0112) —
|
|
38
38
|
// the invite / join-request queue behind private goals. Every helper below shapes
|
|
@@ -48,7 +48,7 @@ const MEMBERSHIP_REQUEST_COLS =
|
|
|
48
48
|
// goals) inserts no member — those goals have no owner; Archons act as one.
|
|
49
49
|
|
|
50
50
|
async function createGoal(
|
|
51
|
-
{ versionId, title, description = null, scopeModules = [], status = 'open', createdBy = null },
|
|
51
|
+
{ versionId, title, subtitle = null, description = null, scopeModules = [], status = 'open', createdBy = null },
|
|
52
52
|
deps = {}
|
|
53
53
|
) {
|
|
54
54
|
const activePool = (deps.pool && process.env.NODE_ENV === 'test') ? deps.pool : pool;
|
|
@@ -56,15 +56,15 @@ async function createGoal(
|
|
|
56
56
|
`WITH next_order AS (
|
|
57
57
|
SELECT COALESCE(MAX(sort_order) + 1, 0) AS n FROM goals WHERE version_id = $1
|
|
58
58
|
), g AS (
|
|
59
|
-
INSERT INTO goals (version_id, title, description, scope_modules, status, created_by, sort_order)
|
|
60
|
-
SELECT $1, $2, $3, $4, $5, $6, next_order.n FROM next_order
|
|
59
|
+
INSERT INTO goals (version_id, title, subtitle, description, scope_modules, status, created_by, sort_order)
|
|
60
|
+
SELECT $1, $2, $7, $3, $4, $5, $6, next_order.n FROM next_order
|
|
61
61
|
RETURNING ${GOAL_COLS}
|
|
62
62
|
), first_lead AS (
|
|
63
63
|
INSERT INTO goal_members (goal_id, builder_id, membership_kind)
|
|
64
64
|
SELECT g.id, $6::bigint, 'lead' FROM g WHERE $6::bigint IS NOT NULL
|
|
65
65
|
)
|
|
66
66
|
SELECT * FROM g`,
|
|
67
|
-
[versionId, title, description, scopeModules, status, createdBy]
|
|
67
|
+
[versionId, title, description, scopeModules, status, createdBy, subtitle]
|
|
68
68
|
);
|
|
69
69
|
return rows[0];
|
|
70
70
|
}
|
|
@@ -91,24 +91,26 @@ function proseDelta(field, from, to) {
|
|
|
91
91
|
async function updateGoalProse({ goalId, patch = {} }, deps = {}) {
|
|
92
92
|
const activePool = (deps.pool && process.env.NODE_ENV === 'test') ? deps.pool : pool;
|
|
93
93
|
const { rows: before } = await activePool.query(
|
|
94
|
-
`SELECT id, title, description FROM goals WHERE id = $1`,
|
|
94
|
+
`SELECT id, title, subtitle, description FROM goals WHERE id = $1`,
|
|
95
95
|
[goalId]
|
|
96
96
|
);
|
|
97
97
|
if (before.length === 0) return null;
|
|
98
98
|
const prev = before[0];
|
|
99
99
|
const nextTitle = Object.prototype.hasOwnProperty.call(patch, 'title') ? patch.title : prev.title;
|
|
100
|
+
const nextSubtitle = Object.prototype.hasOwnProperty.call(patch, 'subtitle') ? patch.subtitle : prev.subtitle;
|
|
100
101
|
const nextDesc = Object.prototype.hasOwnProperty.call(patch, 'description') ? patch.description : prev.description;
|
|
101
102
|
const deltas = [
|
|
102
103
|
proseDelta('title', prev.title, nextTitle),
|
|
104
|
+
proseDelta('subtitle', prev.subtitle, nextSubtitle),
|
|
103
105
|
proseDelta('description', prev.description, nextDesc),
|
|
104
106
|
].filter(Boolean);
|
|
105
107
|
if (deltas.length === 0) return { goal: prev, deltas: [], unchanged: true };
|
|
106
108
|
const { rows } = await activePool.query(
|
|
107
109
|
`UPDATE goals
|
|
108
|
-
SET title = $2, description = $3, updated_at = now()
|
|
110
|
+
SET title = $2, description = $3, subtitle = $4, updated_at = now()
|
|
109
111
|
WHERE id = $1
|
|
110
|
-
RETURNING id, version_id, title, description, status, visibility, created_by, updated_at`,
|
|
111
|
-
[goalId, nextTitle, nextDesc]
|
|
112
|
+
RETURNING id, version_id, title, subtitle, description, status, visibility, created_by, updated_at`,
|
|
113
|
+
[goalId, nextTitle, nextDesc, nextSubtitle]
|
|
112
114
|
);
|
|
113
115
|
return { goal: rows[0], deltas };
|
|
114
116
|
}
|
|
@@ -219,13 +221,17 @@ function goalProseHandler({ db, authorizeGoalManagement, validateOrRespond, pars
|
|
|
219
221
|
if (id === null) return;
|
|
220
222
|
if (validateOrRespond(req, res, {
|
|
221
223
|
title: { type: 'string', minLength: 1, maxLength: LIMITS.TITLE },
|
|
224
|
+
// No minLength: '' is a legal subtitle, meaning deliberately blank. The
|
|
225
|
+
// column's NULL (never set) is what the UI tells apart from that.
|
|
226
|
+
subtitle: { type: 'string', maxLength: LIMITS.TITLE },
|
|
222
227
|
description: { type: 'string', maxLength: LIMITS.DESCRIPTION },
|
|
223
228
|
})) return;
|
|
224
229
|
const patch = {};
|
|
225
230
|
if (Object.prototype.hasOwnProperty.call(req.body || {}, 'title')) patch.title = req.body.title;
|
|
231
|
+
if (Object.prototype.hasOwnProperty.call(req.body || {}, 'subtitle')) patch.subtitle = req.body.subtitle;
|
|
226
232
|
if (Object.prototype.hasOwnProperty.call(req.body || {}, 'description')) patch.description = req.body.description;
|
|
227
233
|
if (Object.keys(patch).length === 0) {
|
|
228
|
-
return res.fail('nothing_to_update', 400, { editable: ['title', 'description'] });
|
|
234
|
+
return res.fail('nothing_to_update', 400, { editable: ['title', 'subtitle', 'description'] });
|
|
229
235
|
}
|
|
230
236
|
try {
|
|
231
237
|
const goal = await db.getGoal(id);
|
|
@@ -438,7 +438,7 @@ module.exports = function buildGoalsRouter() {
|
|
|
438
438
|
if (validateOrRespond(req, res, {
|
|
439
439
|
version_id: { required: true, type: 'string', minLength: 1, maxLength: 64 },
|
|
440
440
|
title: { required: true, type: 'string', minLength: 1, maxLength: LIMITS.TITLE },
|
|
441
|
-
description: { type: 'string', maxLength: LIMITS.DESCRIPTION },
|
|
441
|
+
subtitle: { type: 'string', maxLength: LIMITS.TITLE }, description: { type: 'string', maxLength: LIMITS.DESCRIPTION },
|
|
442
442
|
scope_modules: { type: 'array', maxItems: 64, itemsType: 'string' },
|
|
443
443
|
succeeds_goal_id: { type: 'integer', min: 1 },
|
|
444
444
|
})) return;
|
|
@@ -474,7 +474,7 @@ module.exports = function buildGoalsRouter() {
|
|
|
474
474
|
const goal = await db.createGoal({
|
|
475
475
|
versionId: req.body.version_id,
|
|
476
476
|
title: req.body.title,
|
|
477
|
-
description: (req.body.description || '').trim() || null,
|
|
477
|
+
subtitle: (req.body.subtitle || '').trim() || null, description: (req.body.description || '').trim() || null,
|
|
478
478
|
scopeModules,
|
|
479
479
|
createdBy: req.builder.id,
|
|
480
480
|
});
|
|
@@ -640,7 +640,7 @@ module.exports = function buildGoalsRouter() {
|
|
|
640
640
|
}
|
|
641
641
|
});
|
|
642
642
|
|
|
643
|
-
// PATCH /goals/:id — correct a goal's own title/description (
|
|
643
|
+
// PATCH /goals/:id — correct a goal's own title/subtitle/description (tasks 1003102, 1003224).
|
|
644
644
|
// Handler + rationale + rank note: ../goal-edits.js. Pinned in route-rank-check.
|
|
645
645
|
router.patch('/goals/:id', auth.requireBuilder, goalEdits.goalProseHandler({ db, authorizeGoalManagement, validateOrRespond, parseId, LIMITS }));
|
|
646
646
|
// PATCH /goals/:id/visibility — make a goal public|private (task 1952 / ADR 0112).
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.324",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.324",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.324",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
|
@@ -32,6 +32,7 @@ const fs = require('node:fs');
|
|
|
32
32
|
const path = require('node:path');
|
|
33
33
|
const { execFileSync } = require('node:child_process');
|
|
34
34
|
const { apiCall, cliClient, arg } = require('./cli-lib');
|
|
35
|
+
const { resolveDefaultVersion } = require('./version-select.js');
|
|
35
36
|
const grader = require('../../modules/grading/grader');
|
|
36
37
|
const llmCache = require('../../src/bongos/llm-cache'); // System 5 (task 1360): cache identical audits over an unchanged tree
|
|
37
38
|
const { branding } = require('../../src/branding');
|
|
@@ -183,7 +184,12 @@ async function main() {
|
|
|
183
184
|
const args = process.argv.slice(2);
|
|
184
185
|
const scope = arg('--scope', args);
|
|
185
186
|
const paths = arg('--paths', args);
|
|
186
|
-
|
|
187
|
+
// task 1003166 (G02): was 'GDS-V3', long since shipped.
|
|
188
|
+
const version = arg('--version', args) || await resolveDefaultVersion();
|
|
189
|
+
if (!version) {
|
|
190
|
+
console.error('architect-audit: no version is building; pass --version <id>.');
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
187
193
|
const dryRun = args.includes('--dry-run');
|
|
188
194
|
|
|
189
195
|
if (!scope || !paths) {
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
const { touchesOverlap } = require('../../src/bongos/path-match.js');
|
|
23
23
|
const { cliClient } = require('./cli-lib');
|
|
24
|
+
const { resolveDefaultVersion } = require('./version-select.js');
|
|
24
25
|
// task 1003153 — the DEAD-edge detector core, shared with the claim refusal so
|
|
25
26
|
// "abandoned dependency" means one thing across the CLI, the route and this audit.
|
|
26
27
|
const { findStrandedTasks } = require('../../modules/lifecycle/dead-deps.js');
|
|
@@ -128,7 +129,16 @@ async function main() {
|
|
|
128
129
|
process.exitCode = await reportStranded({ asJson: argv.includes('--json') });
|
|
129
130
|
return;
|
|
130
131
|
}
|
|
131
|
-
|
|
132
|
+
// task 1003166 (G02): the default was 'GDS-V3', which has shipped — so a bare
|
|
133
|
+
// `audit-deps` audited a closed version and reported nothing useful.
|
|
134
|
+
const ver = argv.includes('--version')
|
|
135
|
+
? argv[argv.indexOf('--version') + 1]
|
|
136
|
+
: await resolveDefaultVersion();
|
|
137
|
+
if (!ver) {
|
|
138
|
+
console.error('audit-deps: no version is building; pass --version <id>.');
|
|
139
|
+
process.exitCode = 1;
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
132
142
|
const minRank = argv.includes('--min') ? argv[argv.indexOf('--min') + 1] : 'low';
|
|
133
143
|
const asJson = argv.includes('--json');
|
|
134
144
|
const order = { high: 3, medium: 2, low: 1 };
|
package/scripts/gds/capture.js
CHANGED
|
@@ -54,22 +54,10 @@ const readline = require('node:readline');
|
|
|
54
54
|
const { cliClient, requireSession, arg, hasFlag } = require('./cli-lib');
|
|
55
55
|
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const versions = r.data?.versions ?? [];
|
|
62
|
-
// Most recent building version. Fall back to any building version if
|
|
63
|
-
// started_at is null on all of them.
|
|
64
|
-
const building = versions.filter((v) => v.status === 'building');
|
|
65
|
-
if (building.length === 0) return null;
|
|
66
|
-
building.sort((a, b) => {
|
|
67
|
-
const ta = a.started_at ? new Date(a.started_at).getTime() : 0;
|
|
68
|
-
const tb = b.started_at ? new Date(b.started_at).getTime() : 0;
|
|
69
|
-
return tb - ta;
|
|
70
|
-
});
|
|
71
|
-
return building[0].id;
|
|
72
|
-
}
|
|
57
|
+
// task 1003166 (G02): the selection rule moved to version-select.js so the nine
|
|
58
|
+
// scripts that used to hardcode a version id share ONE implementation. Kept as a
|
|
59
|
+
// local name so every call site below reads unchanged.
|
|
60
|
+
const { resolveDefaultVersion } = require('./version-select.js');
|
|
73
61
|
|
|
74
62
|
// Full JSON, not console.error's depth-2 inspect — a validation 400's nested
|
|
75
63
|
// details otherwise print as "[ [Object] ]" and hide which field failed.
|
package/scripts/gds/fitness.js
CHANGED
|
@@ -1910,7 +1910,7 @@ const CHECKS = [
|
|
|
1910
1910
|
checkWriteRoutesValidated, // Check 18 — BV1 R12 / task 1999: every body-reading write route validates (ADR 0118)
|
|
1911
1911
|
require('./fitness-ratchets.js').checkQualityRatchets, // Check 20 — task 1003129: quality budgets only tighten (mechanics + knip CI step live in fitness-ratchets.js)
|
|
1912
1912
|
require('./doc-cli-guard.js').checkDocNamesRealCli, // Check 22 — task 1003165 / G01: a doc that names a CLI invocation must match the real CLI
|
|
1913
|
-
require('./skill-preflight.js').checkOpsSkillsDeclareMachine, //
|
|
1913
|
+
require('./skill-preflight.js').checkOpsSkillsDeclareMachine, require('./version-literal-guard.js').checkNoVersionLiterals, // Checks 23 (task 1003167/G03: a skill declares its machine) + 24 (task 1003166/G02: no script hardcodes a version id) — one line because fitness.js is 500 over the excess-lines ratchet and a new check must not cost a line (idea 1000810)
|
|
1914
1914
|
require('./control-manifest.js').checkControlsExist, // Check 21 — task 1003131: goal-1000076 controls assert their own existence (the ADR 0022 assurance-of-assurance)
|
|
1915
1915
|
];
|
|
1916
1916
|
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
// Pure helper (no I/O) — resolves the per-rank wandering default so the
|
|
20
20
|
// off-task signal below only fires for the CONSTRAINING ranks (task 1268).
|
|
21
21
|
const wanderingPrefs = require('../../modules/builder-settings/wandering-prefs'); // carved → modules/builder-settings/ (BV1.R80 / ADR 0093 §1)
|
|
22
|
+
const { resolveDefaultVersion } = require('./version-select.js');
|
|
22
23
|
|
|
23
24
|
// ---- classification vocabularies -----------------------------------------
|
|
24
25
|
|
|
@@ -547,6 +548,9 @@ if (require.main === module) {
|
|
|
547
548
|
: []);
|
|
548
549
|
let filed = 0;
|
|
549
550
|
let skipped = 0;
|
|
551
|
+
// task 1003166 (G02): resolved ONCE — it is invariant for the run, and this
|
|
552
|
+
// is a network round trip. The first version of this called it per finding.
|
|
553
|
+
const suggestedVersion = await resolveDefaultVersion();
|
|
550
554
|
for (const s of systemic) {
|
|
551
555
|
const tmpl = FINDING_TEMPLATES[s.type] || { title: s.type, body: '' };
|
|
552
556
|
const title = `${PREFIX} ${tmpl.title}`.slice(0, 200);
|
|
@@ -561,7 +565,8 @@ if (require.main === module) {
|
|
|
561
565
|
body: {
|
|
562
566
|
title,
|
|
563
567
|
body_md: bodyMd,
|
|
564
|
-
|
|
568
|
+
// task 1003166 (G02): was 'GDS-V4', which has shipped.
|
|
569
|
+
suggested_version: suggestedVersion,
|
|
565
570
|
},
|
|
566
571
|
}).catch(() => null);
|
|
567
572
|
if (r && r.ok) filed++;
|
package/scripts/gds/ship-flow.js
CHANGED
|
@@ -20,6 +20,10 @@ const { autoLinkifyChangedDocs, mergeMainIntoBranch, preflight } = require('./sh
|
|
|
20
20
|
const { applyBaseOverride, computeChangedFiles, computeGraderBaseline, runSmokeAsync } = require('./ship-grade.js');
|
|
21
21
|
const { buildPriorRoundContext, computeGradeResult, gradeAndRecord, printCreditsAndAchievements, printGradeOutcome } = require('./ship-grade-steps.js');
|
|
22
22
|
const { apiErrorLine, normalizeApiError } = require('./ship-io.js');
|
|
23
|
+
// task 1003205's carve left pushVia() used at regradeMain but never imported, so
|
|
24
|
+
// EVERY resume of a 'completed' task died with ReferenceError before resolving.
|
|
25
|
+
const { pushVia } = require('./ship-deploy-target.js');
|
|
26
|
+
|
|
23
27
|
const { diffstatFor, finishShip } = require('./ship-finish.js');
|
|
24
28
|
const { evaluateRegradeEligibility, evaluateResumeAction, resumeMerge } = require('./ship-resume.js');
|
|
25
29
|
const { noteServerReward } = require('./ship-state.js');
|
|
@@ -24,6 +24,14 @@ source "$(dirname "$0")/lib.sh"
|
|
|
24
24
|
API="$(gds_api_base)"
|
|
25
25
|
TOKEN="$(gds_resolve_token)" || exit $?
|
|
26
26
|
|
|
27
|
+
# task 1003166 (G02): resolve the version at RUNTIME rather than naming one. A
|
|
28
|
+
# literal works until that version ships, then `version_closed` refuses every
|
|
29
|
+
# create and the smoke fails as if the code broke (idea 1000823).
|
|
30
|
+
VERSION_ID=$(node "$(dirname "$0")/version-select.js") || {
|
|
31
|
+
echo "FAIL: could not resolve a building version — see version-select.js" >&2
|
|
32
|
+
exit 1
|
|
33
|
+
}
|
|
34
|
+
|
|
27
35
|
ts=$(date +%Y%m%d-%H%M%S)
|
|
28
36
|
|
|
29
37
|
# Wrapper: METHOD PATH [BODY] → echoes "HTTP_CODE\nPAYLOAD". Never exits.
|
|
@@ -49,7 +57,7 @@ mk_task() {
|
|
|
49
57
|
import json,sys
|
|
50
58
|
t = '${touches_csv}'.split(',')
|
|
51
59
|
print(json.dumps({
|
|
52
|
-
'version_id': '
|
|
60
|
+
'version_id': '${VERSION_ID}',
|
|
53
61
|
'title': '__smoke__ batch-claim ${ts}-${suffix}',
|
|
54
62
|
'description': 'smoke',
|
|
55
63
|
'status': 'ready',
|
|
@@ -31,6 +31,14 @@ source "$(dirname "$0")/lib.sh"
|
|
|
31
31
|
API="$(gds_api_base)"
|
|
32
32
|
TOKEN="$(gds_resolve_token)" || exit $?
|
|
33
33
|
|
|
34
|
+
# task 1003166 (G02): resolve the version at RUNTIME rather than naming one. A
|
|
35
|
+
# literal works until that version ships, then `version_closed` refuses every
|
|
36
|
+
# create and the smoke fails as if the code broke (idea 1000823).
|
|
37
|
+
VERSION_ID=$(node "$(dirname "$0")/version-select.js") || {
|
|
38
|
+
echo "FAIL: could not resolve a building version — see version-select.js" >&2
|
|
39
|
+
exit 1
|
|
40
|
+
}
|
|
41
|
+
|
|
34
42
|
ts=$(date +%Y%m%d-%H%M%S)
|
|
35
43
|
|
|
36
44
|
# Wrapper: METHOD PATH [BODY] → echoes "PAYLOAD\nHTTP_CODE". Never exits.
|
|
@@ -55,7 +63,7 @@ mk_task() {
|
|
|
55
63
|
import json,sys
|
|
56
64
|
t = '${touches_csv}'.split(',')
|
|
57
65
|
print(json.dumps({
|
|
58
|
-
'version_id': '
|
|
66
|
+
'version_id': '${VERSION_ID}',
|
|
59
67
|
'title': '__smoke__ batch-release ${ts}-${suffix}',
|
|
60
68
|
'description': 'smoke',
|
|
61
69
|
'status': 'ready',
|
|
@@ -86,7 +86,7 @@ create_task() {
|
|
|
86
86
|
body=$(python3 -c "
|
|
87
87
|
import json
|
|
88
88
|
print(json.dumps({
|
|
89
|
-
'version_id': '
|
|
89
|
+
'version_id': '${VERSION_ID}',
|
|
90
90
|
'title': '${title}',
|
|
91
91
|
'description': 'Synthetic smoke task. Auto-cleaned by smoke-claim-consistency.sh.',
|
|
92
92
|
'status': 'ready',
|
|
@@ -124,6 +124,15 @@ echo "API: ${API}"
|
|
|
124
124
|
echo "Titles: ${SMOKE_TITLE_A} | ${SMOKE_TITLE_B}"
|
|
125
125
|
echo
|
|
126
126
|
|
|
127
|
+
# task 1003166 (G02): resolve the version at RUNTIME. This file used to hardcode
|
|
128
|
+
# one; that version shipped, `version_closed` refused every create, and the smoke
|
|
129
|
+
# could not pass while /merge-mode still advertised it green (idea 1000823).
|
|
130
|
+
VERSION_ID=$(node "$(dirname "$0")/version-select.js") || {
|
|
131
|
+
echo "FAIL: could not resolve a building version — see version-select.js" >&2
|
|
132
|
+
exit 1
|
|
133
|
+
}
|
|
134
|
+
echo "Version: ${VERSION_ID}"
|
|
135
|
+
|
|
127
136
|
echo "--- 1. create 2 smoke tasks ---"
|
|
128
137
|
TASK_A=$(create_task "${SMOKE_TITLE_A}" "A")
|
|
129
138
|
TASK_B=$(create_task "${SMOKE_TITLE_B}" "B")
|
|
@@ -58,6 +58,15 @@ echo "API: ${API}"
|
|
|
58
58
|
echo "Title: ${SMOKE_TITLE}"
|
|
59
59
|
echo
|
|
60
60
|
|
|
61
|
+
# task 1003166 (G02): resolve the version at RUNTIME. This file used to hardcode
|
|
62
|
+
# one; that version shipped, `version_closed` refused every create, and the smoke
|
|
63
|
+
# could not pass while /merge-mode still advertised it green (idea 1000823).
|
|
64
|
+
VERSION_ID=$(node "$(dirname "$0")/version-select.js") || {
|
|
65
|
+
echo "FAIL: could not resolve a building version — see version-select.js" >&2
|
|
66
|
+
exit 1
|
|
67
|
+
}
|
|
68
|
+
echo "Version: ${VERSION_ID}"
|
|
69
|
+
|
|
61
70
|
echo "--- 0. snapshot progress before ---"
|
|
62
71
|
PROGRESS_BEFORE=$(snapshot_progress)
|
|
63
72
|
echo "${PROGRESS_BEFORE}"
|
|
@@ -67,7 +76,7 @@ echo "--- 1. create task ---"
|
|
|
67
76
|
CREATE_BODY=$(python3 -c "
|
|
68
77
|
import json
|
|
69
78
|
print(json.dumps({
|
|
70
|
-
'version_id': '
|
|
79
|
+
'version_id': '${VERSION_ID}',
|
|
71
80
|
'title': '${SMOKE_TITLE}',
|
|
72
81
|
'description': 'Synthetic smoke task. Auto-shipped by smoke-write.sh.',
|
|
73
82
|
'status': 'ready',
|
package/scripts/gds/triage.js
CHANGED
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
const readline = require('node:readline');
|
|
39
39
|
const fs = require('node:fs');
|
|
40
40
|
const { cliClient, requireSession } = require('./cli-lib');
|
|
41
|
+
const { resolveDefaultVersion } = require('./version-select.js');
|
|
41
42
|
|
|
42
43
|
function prompt(rl, question) {
|
|
43
44
|
return new Promise((resolve) => rl.question(question, (a) => resolve(a)));
|
|
@@ -384,7 +385,9 @@ async function main() {
|
|
|
384
385
|
|
|
385
386
|
if (ans === 'p' || ans === 'promote') {
|
|
386
387
|
// Promote: version, priority, title, goal — each defaulted from the idea.
|
|
387
|
-
|
|
388
|
+
// task 1003166 (G02): the fallback was 'PMS-V2', which has shipped —
|
|
389
|
+
// promoting into it would have been refused as version_closed.
|
|
390
|
+
const defaultVersion = idea.suggested_version || await resolveDefaultVersion();
|
|
388
391
|
const versionAns = (await prompt(rl, ` version [${defaultVersion}]: `)).trim();
|
|
389
392
|
const versionId = versionAns || defaultVersion;
|
|
390
393
|
|