@bongos/core 1.21.107 → 1.21.109
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 +43 -38
- package/.claude/skills/goal-create/SKILL.md +9 -4
- package/.claude/skills/idea-triage/SKILL.md +1 -1
- package/.claude/skills/planning-session/SKILL.md +22 -9
- package/docs/copy-inventory.md +25 -19
- package/docs/copy-registry.json +92 -37
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-readings.json +189 -188
- package/modules/copy-desk/docx.js +27 -7
- package/modules/copy-desk/page-status.js +3 -1
- package/modules/copy-desk/pages.js +44 -5
- package/modules/copy-desk/routes/copy-desk.js +4 -3
- package/modules/copy-desk/tests/copy_docx.mjs +38 -0
- package/modules/copy-desk/tests/copy_page_draft.mjs +79 -0
- package/modules/hall-ui/public/paintbrush.js +152 -12
- package/modules/hall-ui/public/studio.js +3 -1
- package/modules/hall-ui/public/style.css +16 -0
- package/modules/hall-ui/public/tweak-editor-lib.js +24 -12
- package/modules/hall-ui/records/craft-mode.md +5 -0
- package/modules/hall-ui/records/tweak-editor.md +1 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +20 -0
- package/scripts/gds/seed-bv2-ideator-surface-tasks.js +12 -21
- package/scripts/gds/seed-verify-criteria.js +73 -18
- package/scripts/hall-preview/server.js +12 -1
- package/src/module-api.js +1 -1
- package/tests/copy_desk_page_submit.mjs +36 -0
- package/tests/hall_preview_encoded_paths.mjs +81 -0
- package/tests/hall_tweak_editor.mjs +19 -3
- package/tests/paintbrush.mjs +151 -2
- package/tests/planning_session_skill.mjs +102 -4
- package/tests/seed_verify_criteria.mjs +130 -0
|
@@ -15,11 +15,13 @@
|
|
|
15
15
|
|
|
16
16
|
import { strict as assert } from 'node:assert';
|
|
17
17
|
import { createRequire } from 'node:module';
|
|
18
|
+
import { readFileSync } from 'node:fs';
|
|
18
19
|
|
|
19
20
|
const require = createRequire(import.meta.url);
|
|
20
21
|
const {
|
|
21
22
|
diffCriterionLinks,
|
|
22
23
|
expectedCriterionCounts,
|
|
24
|
+
baselineSeedCriteria,
|
|
23
25
|
verifySeedCriteriaLinks,
|
|
24
26
|
} = require('../scripts/gds/seed-verify-criteria.js');
|
|
25
27
|
|
|
@@ -145,5 +147,133 @@ await testAsync('a failed/malformed GET is treated as a verification failure, no
|
|
|
145
147
|
assert.ok(logs.some((l) => l.includes('VERIFY FAILED')));
|
|
146
148
|
});
|
|
147
149
|
|
|
150
|
+
// --- task 1004668: seeding into a goal that already carries linked work -----
|
|
151
|
+
//
|
|
152
|
+
// Goal 1000095 held six criteria with live links (1, 9, 4, 3, 6, 10) before a
|
|
153
|
+
// seed touched it. The old diff unioned those into the check with an expected
|
|
154
|
+
// count of zero, so a CORRECT seed exited 1 six times over.
|
|
155
|
+
|
|
156
|
+
const POPULATED = [
|
|
157
|
+
{ criterion_id: 'old-1', linked_tasks: 1 }, { criterion_id: 'old-2', linked_tasks: 9 },
|
|
158
|
+
{ criterion_id: 'old-3', linked_tasks: 4 }, { criterion_id: 'old-4', linked_tasks: 3 },
|
|
159
|
+
{ criterion_id: 'old-5', linked_tasks: 6 }, { criterion_id: 'old-6', linked_tasks: 10 },
|
|
160
|
+
];
|
|
161
|
+
|
|
162
|
+
test('THE INCIDENT: without a baseline, criteria the seed does not cite are not assumed to be zero', () => {
|
|
163
|
+
const rows = [...POPULATED, { criterion_id: 'ours', linked_tasks: 2 }];
|
|
164
|
+
assert.deepEqual(diffCriterionLinks({ ours: 2 }, rows), []);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test('without a baseline the plan\'s own criteria are still checked', () => {
|
|
168
|
+
const rows = [...POPULATED, { criterion_id: 'ours', linked_tasks: 0 }];
|
|
169
|
+
assert.deepEqual(diffCriterionLinks({ ours: 2 }, rows), [{ criterion_id: 'ours', expected: 2, actual: 0 }]);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test('with a baseline, an untouched criterion at its pre-seed count passes', () => {
|
|
173
|
+
const baseline = { 'old-1': 1, 'old-2': 9, 'old-3': 4, 'old-4': 3, 'old-5': 6, 'old-6': 10, ours: 0 };
|
|
174
|
+
const rows = [...POPULATED, { criterion_id: 'ours', linked_tasks: 2 }];
|
|
175
|
+
assert.deepEqual(diffCriterionLinks({ ours: 2 }, rows, { baseline }), []);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test('with a baseline, a stray link onto a criterion the seed never cites is caught', () => {
|
|
179
|
+
// The RETRO2 mislink lands tasks on the WRONG criterion. With a baseline that
|
|
180
|
+
// excess is visible even though the wrong criterion is not in the plan.
|
|
181
|
+
const baseline = { 'old-1': 1, ours: 0 };
|
|
182
|
+
const rows = [{ criterion_id: 'old-1', linked_tasks: 3 }, { criterion_id: 'ours', linked_tasks: 0 }];
|
|
183
|
+
assert.deepEqual(diffCriterionLinks({ ours: 2 }, rows, { baseline }), [
|
|
184
|
+
{ criterion_id: 'old-1', expected: 1, actual: 3 },
|
|
185
|
+
{ criterion_id: 'ours', expected: 2, actual: 0 },
|
|
186
|
+
]);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test('with a baseline, a planned criterion that already had links expects baseline + plan', () => {
|
|
190
|
+
const rows = [{ criterion_id: 'shared', linked_tasks: 7 }];
|
|
191
|
+
assert.deepEqual(diffCriterionLinks({ shared: 2 }, rows, { baseline: { shared: 5 } }), []);
|
|
192
|
+
assert.deepEqual(diffCriterionLinks({ shared: 2 }, rows, { baseline: { shared: 4 } }),
|
|
193
|
+
[{ criterion_id: 'shared', expected: 6, actual: 7 }]);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test('a criterion created after the baseline starts at zero, so a link on it is caught', () => {
|
|
197
|
+
const rows = [{ criterion_id: 'brand-new', linked_tasks: 1 }];
|
|
198
|
+
assert.deepEqual(diffCriterionLinks({}, rows, { baseline: {} }),
|
|
199
|
+
[{ criterion_id: 'brand-new', expected: 0, actual: 1 }]);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const goalWith = (criteria) => async () => ({ ok: true, status: 200, json: { criteria } });
|
|
203
|
+
|
|
204
|
+
// A RESUMED seed (grade finding on task 1004668). The first run created task A
|
|
205
|
+
// (linked to `ours`) and died; the re-run baselines AFTER that, replays A
|
|
206
|
+
// (`idempotent: true`) and creates B. The plan must count only B — tallying the
|
|
207
|
+
// replay too expects A's link twice and fails a correct resume.
|
|
208
|
+
function tallyCreated(responses) {
|
|
209
|
+
// The reference seed's tally rule, exactly: skip a replayed create.
|
|
210
|
+
return expectedCriterionCounts(responses.filter((r) => !r.idempotent).map((r) => r.crit));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
test('a resumed seed tallies only what THIS run created, and verifies OK', () => {
|
|
214
|
+
const baseline = { ours: 1 }; // A's link, from the first run
|
|
215
|
+
const responses = [{ crit: ['ours'], idempotent: true }, { crit: ['ours'] }];
|
|
216
|
+
const rows = [{ criterion_id: 'ours', linked_tasks: 2 }];
|
|
217
|
+
assert.deepEqual(diffCriterionLinks(tallyCreated(responses), rows, { baseline }), []);
|
|
218
|
+
// The bug the finding names: counting the replay expects 3.
|
|
219
|
+
const naive = expectedCriterionCounts(responses.map((r) => r.crit));
|
|
220
|
+
assert.deepEqual(diffCriterionLinks(naive, rows, { baseline }), [{ criterion_id: 'ours', expected: 3, actual: 2 }]);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test('a fully re-run seed (every create a replay) plans nothing and verifies OK', () => {
|
|
224
|
+
const responses = [{ crit: ['ours'], idempotent: true }, { crit: ['ours'], idempotent: true }];
|
|
225
|
+
assert.deepEqual(diffCriterionLinks(tallyCreated(responses), [{ criterion_id: 'ours', linked_tasks: 2 }], { baseline: { ours: 2 } }), []);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test('the baselined reference seed skips replays when it tallies', () => {
|
|
229
|
+
const src = readFileSync(new URL('../scripts/gds/seed-bv2-ideator-surface-tasks.js', import.meta.url), 'utf8');
|
|
230
|
+
assert.match(src, /baselineSeedCriteria\(/, 'it takes a baseline');
|
|
231
|
+
assert.match(src, /if \(!r\.json\.idempotent\) criterionIdListsPerTask\.push\(t\.crit\);/, 'and tallies only created tasks');
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
await testAsync('baselineSeedCriteria snapshots the goal before any write', async () => {
|
|
235
|
+
const logs = [];
|
|
236
|
+
const baseline = await baselineSeedCriteria({
|
|
237
|
+
goalId: 1000095, fetchGoal: goalWith(POPULATED), log: (l) => logs.push(l), exit: () => { throw new Error('should not exit'); },
|
|
238
|
+
});
|
|
239
|
+
assert.deepEqual(baseline, { 'old-1': 1, 'old-2': 9, 'old-3': 4, 'old-4': 3, 'old-5': 6, 'old-6': 10 });
|
|
240
|
+
assert.ok(logs.some((l) => l.includes('6 already carrying linked tasks')));
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
await testAsync('baselineSeedCriteria refuses to seed blind when the goal cannot be read', async () => {
|
|
244
|
+
let exitCode = null; const logs = [];
|
|
245
|
+
await baselineSeedCriteria({
|
|
246
|
+
goalId: 1000095, fetchGoal: async () => ({ ok: false, status: 401, json: null }),
|
|
247
|
+
log: (l) => logs.push(l), exit: (c) => { exitCode = c; },
|
|
248
|
+
});
|
|
249
|
+
assert.equal(exitCode, 1);
|
|
250
|
+
assert.ok(logs.some((l) => l.includes('Refusing to seed blind')));
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
await testAsync('a correct seed into a populated goal verifies OK end to end, baseline passed through', async () => {
|
|
254
|
+
const logs = []; let exitCalled = false;
|
|
255
|
+
const baseline = await baselineSeedCriteria({ goalId: 1000095, fetchGoal: goalWith(POPULATED), log: () => {} });
|
|
256
|
+
await verifySeedCriteriaLinks({
|
|
257
|
+
goalId: 1000095,
|
|
258
|
+
expectedCounts: expectedCriterionCounts([['ours'], ['ours']]),
|
|
259
|
+
baseline,
|
|
260
|
+
fetchGoal: goalWith([...POPULATED, { criterion_id: 'ours', linked_tasks: 2 }]),
|
|
261
|
+
log: (l) => logs.push(l),
|
|
262
|
+
exit: () => { exitCalled = true; },
|
|
263
|
+
});
|
|
264
|
+
assert.equal(exitCalled, false, logs.join('\n'));
|
|
265
|
+
assert.ok(logs.some((l) => l.includes('VERIFY OK') && l.includes('pre-seed count')));
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
await testAsync('without a baseline the OK line says what it did not check', async () => {
|
|
269
|
+
const logs = [];
|
|
270
|
+
await verifySeedCriteriaLinks({
|
|
271
|
+
goalId: 1000095, expectedCounts: { ours: 2 },
|
|
272
|
+
fetchGoal: goalWith([...POPULATED, { criterion_id: 'ours', linked_tasks: 2 }]),
|
|
273
|
+
log: (l) => logs.push(l), exit: () => { throw new Error('should not exit'); },
|
|
274
|
+
});
|
|
275
|
+
assert.ok(logs.some((l) => /were not checked/.test(l) && l.includes('baselineSeedCriteria')));
|
|
276
|
+
});
|
|
277
|
+
|
|
148
278
|
console.log(`\nseed_verify_criteria: ${passed} passed, ${failed} failed`);
|
|
149
279
|
if (failed > 0) process.exit(1);
|