@bongos/core 1.24.14 → 1.24.15
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 +296 -136
- package/.claude/skills/blocker-review/SKILL.md +2 -0
- package/.claude/skills/session-board/SKILL.md +29 -0
- package/.claude/skills/strand-fix/SKILL.md +1 -1
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +14 -0
- package/clients/bongos-client/index.cjs +14 -0
- package/clients/bongos-client/index.d.ts +23 -2
- package/clients/bongos-client/index.mjs +14 -0
- package/docs/adr/0385-money-lives-with-a-processor-credits-stay-a-project-meter.md +4 -0
- package/docs/api/openapi.json +369 -6
- package/docs/api-reference.md +19 -4
- package/docs/architecture.md +6 -0
- package/docs/copy-inventory.md +138 -123
- package/docs/copy-registry.json +296 -171
- package/docs/file-map.md +1 -0
- package/docs/module-api-changelog.md +2 -2
- package/docs/page-inventory.json +6 -1
- package/docs/page-readings.json +1040 -1021
- package/migrations/core_289_task_short_name.sql +34 -0
- package/modules/ai-studio/db.js +10 -0
- package/modules/ai-studio/gemini.js +73 -5
- package/modules/ai-studio/openrouter.js +17 -2
- package/modules/ai-studio/routes/ai-studio.js +7 -1
- package/modules/ai-studio/studio.js +8 -0
- package/modules/builder-settings/checkout-freshness.js +71 -0
- package/modules/builder-settings/routes/settings.js +26 -0
- package/modules/builder-settings/settings.js +2 -0
- package/modules/economy/migrations/economy_002_project_account.sql +83 -0
- package/modules/economy/module.json +1 -1
- package/modules/economy/project-account.js +166 -0
- package/modules/economy/routes/project-account.js +40 -0
- package/modules/hall-ui/public/ai-studio.css +3 -0
- package/modules/hall-ui/public/ai-studio.html +2 -1
- package/modules/hall-ui/public/ai-studio.js +72 -1
- package/modules/hall-ui/public/atlas.html +1 -1
- package/modules/hall-ui/public/blockers.html +1 -1
- package/modules/hall-ui/public/board-room.html +1 -1
- package/modules/hall-ui/public/city.html +1 -1
- package/modules/hall-ui/public/collab.html +1 -1
- package/modules/hall-ui/public/commands.html +1 -1
- package/modules/hall-ui/public/copy-desk.html +1 -1
- package/modules/hall-ui/public/deploy.html +1 -1
- package/modules/hall-ui/public/diagrams.html +1 -1
- package/modules/hall-ui/public/drachmae.html +1 -1
- package/modules/hall-ui/public/fleet.html +1 -1
- package/modules/hall-ui/public/gate.html +1 -1
- package/modules/hall-ui/public/github-chip.js +85 -0
- package/modules/hall-ui/public/goal-map.html +1 -1
- package/modules/hall-ui/public/goals.html +1 -1
- package/modules/hall-ui/public/government.html +1 -1
- package/modules/hall-ui/public/idea.html +1 -1
- package/modules/hall-ui/public/ideas.html +1 -1
- package/modules/hall-ui/public/index.html +1 -1
- package/modules/hall-ui/public/market-watch-item.html +2 -2
- package/modules/hall-ui/public/market-watch-item.js +10 -3
- package/modules/hall-ui/public/market-watch-item.states.json +41 -0
- package/modules/hall-ui/public/market-watch-lib.js +36 -5
- package/modules/hall-ui/public/market-watch-rules.html +1 -1
- package/modules/hall-ui/public/market-watch-settings.html +1 -1
- package/modules/hall-ui/public/market-watch.css +5 -0
- package/modules/hall-ui/public/market-watch.html +2 -2
- package/modules/hall-ui/public/modules.html +1 -1
- package/modules/hall-ui/public/my-agents.html +1 -1
- package/modules/hall-ui/public/my-agents.js +3 -0
- package/modules/hall-ui/public/primer.html +1 -1
- package/modules/hall-ui/public/profile.html +1 -1
- package/modules/hall-ui/public/project-settings.html +1 -1
- package/modules/hall-ui/public/ranks.html +1 -1
- package/modules/hall-ui/public/roadmap.html +1 -1
- package/modules/hall-ui/public/roster.html +1 -1
- package/modules/hall-ui/public/sessions.html +1 -1
- package/modules/hall-ui/public/settings.html +1 -1
- package/modules/hall-ui/public/shell.js +22 -22
- package/modules/hall-ui/public/street.html +1 -1
- package/modules/hall-ui/public/studio.html +1 -1
- package/modules/hall-ui/public/style.css +28 -0
- package/modules/hall-ui/public/task.html +1 -1
- package/modules/hall-ui/public/thinking.html +1 -1
- package/modules/hall-ui/public/tweak-editor.html +1 -1
- package/modules/hall-ui/public/watch.html +1 -1
- package/modules/hall-ui/public/work.html +1 -1
- package/modules/ideas/blockers.js +13 -7
- package/modules/lifecycle/db-claim-reads.js +1 -1
- package/modules/lifecycle/db-ship.js +2 -2
- package/modules/lifecycle/db-tasks.js +4 -4
- package/modules/lifecycle/migrations/lifecycle_019_pay_type_gates.sql +50 -0
- package/modules/lifecycle/module.json +1 -0
- package/modules/lifecycle/pay-type-gates.js +180 -0
- package/modules/lifecycle/publish-reconciler.js +31 -1
- package/modules/lifecycle/rebase-gate.js +3 -2
- package/modules/lifecycle/routes/claims.js +3 -2
- package/modules/lifecycle/routes/pay-type-gates.js +59 -0
- package/modules/lifecycle/routes/project-settings.js +4 -0
- package/modules/lifecycle/routes/task-write-routes.js +43 -0
- package/modules/lifecycle/ship-card.js +4 -1
- package/modules/lifecycle/task-name-db.js +55 -0
- package/modules/lifecycle/task-name.js +140 -0
- package/modules/market-watch/db.js +10 -1
- package/modules/market-watch/migrations/market_watch_004_entry_corrections.sql +20 -0
- package/modules/market-watch/pages.js +20 -1
- package/modules/market-watch/proposals.js +57 -7
- package/modules/market-watch/routes/pages.js +2 -0
- package/modules/market-watch/routes/rules.js +4 -0
- package/modules/platform-identity/terms-acceptance.js +9 -9
- package/modules/public-landing/public/account-google.states.json +14 -0
- package/modules/public-landing/public/assets/cosmos.css +12 -0
- package/modules/public-landing/public/assets/hub-bar.js +44 -1
- package/modules/public-landing/public/privacy.html +5 -1
- package/modules/public-landing/public/projects-google.states.json +14 -0
- package/modules/public-landing/public/projects.html +39 -0
- package/modules/public-landing/public/terms.html +12 -2
- package/modules/ui-design/kit/fixtures/me-google.json +10 -0
- package/modules/ui-design/kit/fixtures/me-sign-in-methods-google.json +4 -0
- package/modules/ui-design/kit/serve.js +7 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +49 -1
- package/scripts/bongos/claim.js +4 -2
- package/scripts/bongos/land-watch.js +1 -0
- package/scripts/bongos/report-checkout.js +30 -0
- package/scripts/bongos/run-unit-tests.js +11 -0
- package/scripts/bongos/sessions-board.js +264 -0
- package/scripts/bongos/ship-finish.js +1 -0
- package/scripts/bongos/ship-flow.js +5 -2
- package/scripts/bongos/strand-lease.js +5 -3
- package/scripts/bongos/strand-watch.js +38 -7
- package/scripts/gds/report-checkout.js +7 -0
- package/scripts/gds/sessions-board.js +7 -0
- package/src/bongos/agent-standing-rules.js +13 -3
- package/src/bongos/routes/me.js +7 -1
- package/src/module-api.js +1 -1
- package/tests/agent_standing_rules.mjs +18 -1
- package/tests/ai_studio_billing.mjs +13 -0
- package/tests/ai_studio_gemini_models.mjs +81 -0
- package/tests/ai_studio_requests.mjs +24 -0
- package/tests/blocker_detail_hydration.mjs +5 -3
- package/tests/blocker_tasks_named_db.mjs +74 -0
- package/tests/claim_gate_ci_unblock.mjs +1 -1
- package/tests/github_chip.mjs +217 -0
- package/tests/hall_ai_studio.mjs +129 -5
- package/tests/host_topology_skips.mjs +1 -0
- package/tests/hub_account_page.mjs +3 -2
- package/tests/landing_legal_pages.mjs +3 -2
- package/tests/market_watch_data_db.mjs +6 -0
- package/tests/market_watch_pages.mjs +87 -2
- package/tests/market_watch_rules.mjs +101 -5
- package/tests/market_watch_rules_db.mjs +42 -0
- package/tests/nav_permission_atoms.mjs +1 -1
- package/tests/pay_terms.mjs +8 -0
- package/tests/pay_type_gates.mjs +234 -0
- package/tests/pay_type_gates_db.mjs +84 -0
- package/tests/project_account.mjs +125 -0
- package/tests/project_account_db.mjs +97 -0
- package/tests/self_healing_start.mjs +162 -0
- package/tests/sessions_board.mjs +153 -0
- package/tests/stop_refcheck.mjs +40 -0
- package/tests/task_name.mjs +237 -0
- package/tests/task_name_db.mjs +79 -0
- package/tests/terms_acceptance.mjs +25 -3
- package/tests/terms_grandfather_db.mjs +11 -4
|
@@ -129,9 +129,19 @@
|
|
|
129
129
|
<p><b>These numbers describe; they do not govern.</b> Your cross-project record is descriptive analytics and never authority. What you may do inside any project is decided by that project alone, from its own records, at the moment you ask. A large record grants nothing and a hidden one takes nothing away; no project is required to read it, and none may treat it as permission. A rank shown here is that project’s own standing for you, as that project reported it — the platform holds no rank of its own.</p>
|
|
130
130
|
<p><b>What is public.</b> Once you have signed in with your platform account in your browser, here or to a project, and accepted these terms, your profile and that record are public by default: at your handle’s page, in the people directory, to project owners looking for people to work with, and to search engines. An account that has never signed in that way stays provisional and is published nowhere.</p>
|
|
131
131
|
<p><b>What you control.</b> From your settings you can hide your statistics, drop out of the recruiting directory, or make your account private. Each takes effect immediately, everywhere, including on a page someone already has open. Hiding removes the platform’s combined view; it does not retract the per-project numbers each project publishes itself, so a determined reader could still add them up the hard way.</p>
|
|
132
|
-
<p><b>Your acceptance is recorded.</b> The platform stores the date you accepted these terms, and which version, on your account. It is written when you sign in with your platform account in your browser, here or to a project, and only once — signing in again does not change the date you accepted. Signing in from the command line does not record it: you approve that sign-in on GitHub’s page, where these terms are not shown, so an account that has only signed in that way stays unpublished until you sign in once in your browser. This is version <b>2026-
|
|
132
|
+
<p><b>Your acceptance is recorded.</b> The platform stores the date you accepted these terms, and which version, on your account. It is written when you sign in with your platform account in your browser, here or to a project, and only once — signing in again does not change the date you accepted. When these terms change, your next sign-in that way records that you accepted the new version. Signing in from the command line does not record it: you approve that sign-in on GitHub’s page, where these terms are not shown, so an account that has only signed in that way stays unpublished until you sign in once in your browser. This is version <b>2026-10-30</b>.</p>
|
|
133
133
|
<p><b>Accounts from before these terms.</b> If your account already existed when this version took effect, the platform accepted it on your behalf, rather than waiting for you to sign in, and dated it the day that was done. The record marks that acceptance as grandfathered, so it is never mistaken for one you gave, and your own acceptance replaces it the next time you sign in that way.</p>
|
|
134
134
|
<!--apex-only:end-->
|
|
135
|
+
<h2>AI features</h2>
|
|
136
|
+
<p>Some features, such as AI Studio, send what you submit to an AI model run by another company, reached through OpenRouter. To use them:</p>
|
|
137
|
+
<ul>
|
|
138
|
+
<li>You must be 18 or over. The AI companies only serve adults, so these features stay off until your account records that you are.</li>
|
|
139
|
+
<li>You agree to follow the terms and usage policies of the AI company whose model answers your request, and OpenRouter's own terms. Which company that is shows on <a href="https://openrouter.ai/models">openrouter.ai</a>.</li>
|
|
140
|
+
<li>You may not use what these features produce to train or build a competing AI model, or try to trick a model into breaking its own rules.</li>
|
|
141
|
+
<li>You are responsible for what you submit and for what you do with the results, including that you have the right to use any image or file you upload.</li>
|
|
142
|
+
</ul>
|
|
143
|
+
<p>When you pay with your balance, the price is what the AI company charged plus a small markup, shown before you confirm. When you use your own key, you pay that company directly and its terms apply to you as its customer. AI output can be wrong; check it before you rely on it.</p>
|
|
144
|
+
|
|
135
145
|
<h2>Fair use</h2>
|
|
136
146
|
<p>Don't use {{productName}} to break the law, to attack this site or other people's projects, to mine, spam or otherwise abuse hosted resources, or to harass other builders. The owner may deactivate a builder or take a project offline for breaking these, and may set further house rules inside the hall.</p>
|
|
137
147
|
|
|
@@ -140,7 +150,7 @@
|
|
|
140
150
|
|
|
141
151
|
<h2>Changes</h2>
|
|
142
152
|
<p>These terms ship with the software and change with it. The version served on this page is the one that applies; the repository keeps the history. <a href="{{sourceRepoUrl}}">Read the source.</a></p>
|
|
143
|
-
<p>Questions go through the <a href="/contact">contact page</a>. What is kept about you is on the <a href="/privacy">privacy page</a>. Revised
|
|
153
|
+
<p>Questions go through the <a href="/contact">contact page</a>. What is kept about you is on the <a href="/privacy">privacy page</a>. Revised October 2026.</p>
|
|
144
154
|
</section>
|
|
145
155
|
</main>
|
|
146
156
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_": "A Google-only builder (task 1004996): the stand-in login ~<handle> a Google-born row carries until GitHub is linked. Aliased as me by the *-google states files, beside me-sign-in-methods-google.",
|
|
3
|
+
"builder": {
|
|
4
|
+
"id": "1",
|
|
5
|
+
"github_login": "~builder",
|
|
6
|
+
"display_name": "A Builder",
|
|
7
|
+
"avatar_url": "/__kit/avatar.svg",
|
|
8
|
+
"rank": "archon"
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -585,8 +585,14 @@ function myGuilds(res) {
|
|
|
585
585
|
// The fixture builder is GitHub-born with Google linked, held for the run so a
|
|
586
586
|
// Remove reads back. Each way in carries the route's own unlink_refused, by
|
|
587
587
|
// account-logins.js unlinkRefusal's rule, and DELETE refuses as the route does.
|
|
588
|
+
// A states file may alias `me__sign-in-methods` to a fixture's `methods` (task 1004996: the
|
|
589
|
+
// Link GitHub chip pictures a Google-only person through `me-sign-in-methods-google`).
|
|
588
590
|
let _ways = null;
|
|
589
|
-
function waysHeld() {
|
|
591
|
+
function waysHeld() {
|
|
592
|
+
if (_ways) return _ways;
|
|
593
|
+
if (ALIAS['me__sign-in-methods']) return (_ways = heldFixture('me__sign-in-methods').methods.map((m) => ({ provider: m.provider, email: m.email || null })));
|
|
594
|
+
return (_ways = [{ provider: 'github', email: null }, { provider: 'google', email: 'octo@example.com' }]);
|
|
595
|
+
}
|
|
590
596
|
function wayRefusal(provider, count) { return provider === 'github' ? 'born_with' : count <= 1 ? 'last_way_in' : null; }
|
|
591
597
|
function signInMethods(res) {
|
|
592
598
|
const held = waysHeld();
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.24.
|
|
3
|
+
"version": "1.24.15",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.24.
|
|
9
|
+
"version": "1.24.15",
|
|
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.24.
|
|
3
|
+
"version": "1.24.15",
|
|
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",
|
package/release-notes.json
CHANGED
|
@@ -10922,7 +10922,55 @@
|
|
|
10922
10922
|
"text": "The weekly Market Watch run now also reads the links members added to a tracked item, treating them safely as outside text and staying within its page-reading limit."
|
|
10923
10923
|
}
|
|
10924
10924
|
],
|
|
10925
|
-
"1.24.
|
|
10925
|
+
"1.24.15": [
|
|
10926
|
+
{
|
|
10927
|
+
"id": "1005027",
|
|
10928
|
+
"text": "The automatic checks that gate releases now agree with the new terms rule, so finished work can reach the live site again."
|
|
10929
|
+
},
|
|
10930
|
+
{
|
|
10931
|
+
"id": "1005026",
|
|
10932
|
+
"text": "Approving a correction to a Market Watch log entry works again, and the release lane that puts finished work on the live site is unblocked."
|
|
10933
|
+
},
|
|
10934
|
+
{
|
|
10935
|
+
"id": "1005025",
|
|
10936
|
+
"text": "AI Studio is now adults only and only uses AI providers that do not keep or train on your prompts; the terms and privacy pages explain the AI features, and everyone re-accepts the updated terms at their next sign-in."
|
|
10937
|
+
},
|
|
10938
|
+
{
|
|
10939
|
+
"id": "1004958",
|
|
10940
|
+
"text": "Every task now has a short two or three word name, and the AI writes \"task 1004745 - payout approvals\" instead of a bare number: in the startup notes, the sessions board, claim and ship messages, stuck-ship notes and blockers."
|
|
10941
|
+
},
|
|
10942
|
+
{
|
|
10943
|
+
"id": "1003182",
|
|
10944
|
+
"text": "The project now has an account record that shows every movement of money and why it happened: who was paid, for which task, and under which payment-processor transfer. Entries can never be edited or deleted, a retried payout c"
|
|
10945
|
+
},
|
|
10946
|
+
{
|
|
10947
|
+
"id": "1005024",
|
|
10948
|
+
"text": "The AI Studio works again with your own Google key even when Google retires a model, and you can now paste a picture or take a photo with your camera."
|
|
10949
|
+
},
|
|
10950
|
+
{
|
|
10951
|
+
"id": "1004996",
|
|
10952
|
+
"text": "People who signed in with Google and have not linked GitHub now see a tiny \"Link GitHub\" chip in the top bar, with the engineer icon so it reads as needed for coding. One click starts linking. It disappears once GitHub is link"
|
|
10953
|
+
},
|
|
10954
|
+
{
|
|
10955
|
+
"id": "1005023",
|
|
10956
|
+
"text": "The slop reminder no longer pops up on the app's own status messages, only when someone actually corrects the AI."
|
|
10957
|
+
},
|
|
10958
|
+
{
|
|
10959
|
+
"id": "1005018",
|
|
10960
|
+
"text": "People using a screen reader on Market Watch now hear the right name for the pop-up they opened (\"stop tracking\" or \"send a new scout\") instead of always \"Propose a change\"."
|
|
10961
|
+
},
|
|
10962
|
+
{
|
|
10963
|
+
"id": "1005021",
|
|
10964
|
+
"text": "Each builder's computer now updates its own project folder at the start of a session, saving any old leftovers first, and tells them plainly when it can't."
|
|
10965
|
+
},
|
|
10966
|
+
{
|
|
10967
|
+
"id": "1004957",
|
|
10968
|
+
"text": "Ask the AI \"what are my sessions doing\" (or type /session-board) and it shows a table of everything you have going at once: each session, its task, whether it is building, waiting to land, back from review or stuck, and what i"
|
|
10969
|
+
},
|
|
10970
|
+
{
|
|
10971
|
+
"id": "1005016",
|
|
10972
|
+
"text": "When the owner approves a member's correction to a Market Watch finding, the finding now actually changes (or is marked wrong), the item page shows a small \"corrected\" note, and who, when and why are kept."
|
|
10973
|
+
},
|
|
10926
10974
|
{
|
|
10927
10975
|
"id": "1004906",
|
|
10928
10976
|
"text": "The payment processor is chosen: Stripe Connect. Each project's money will sit in its own Stripe account, so we never hold anyone's money. J.P. Morgan Payments is recorded as the backup. Nothing is bought or opened yet, and th"
|
package/scripts/bongos/claim.js
CHANGED
|
@@ -12,6 +12,7 @@ const path = require('node:path');
|
|
|
12
12
|
const { cliClient, requireSession, fmtTouches, printOnboardingNudge, rewardPolicy, costPlusOnlyNote, detectWorktreeName, cliExit } = require('./cli-lib');
|
|
13
13
|
const { nextNumbers, padAdr, padMig } = require('./next-number');
|
|
14
14
|
const { CORE_NS } = require('./migration-namespace');
|
|
15
|
+
const { taskRef } = require('../../modules/lifecycle/task-name.js'); // task 1004958
|
|
15
16
|
const { contextPack, formatContextPack, resolveWorktreeRoot } = require('./context-pack');
|
|
16
17
|
const { printClaimRecall } = require('./claim-recall');
|
|
17
18
|
const { decideWorktreeClaim, ESCAPE_FLAG } = require('./worktree-claim-guard');
|
|
@@ -238,6 +239,7 @@ function printClaimCard(task, widgetsEnabled = true) {
|
|
|
238
239
|
const model = shipCard.buildCardModel({
|
|
239
240
|
stage: 'claimed',
|
|
240
241
|
taskId: task.id,
|
|
242
|
+
shortName: task.short_name,
|
|
241
243
|
title: task.title,
|
|
242
244
|
description: task.description || '',
|
|
243
245
|
estMinutes: task.est_minutes != null ? Number(task.est_minutes) : null,
|
|
@@ -391,13 +393,13 @@ function claimFailureGuidance({ code, message, d }, r, taskId, ctx) {
|
|
|
391
393
|
// so this one leaves it alone instead of piling onto the same /strand-fix.
|
|
392
394
|
const leases = (ctx && ctx.strandLeases) || [];
|
|
393
395
|
for (const t of owed) {
|
|
394
|
-
lines.push(` -
|
|
396
|
+
lines.push(` - ${taskRef(t.id, t.short_name)}${t.title ? ` ${clip(t.title)}` : ''}`);
|
|
395
397
|
if (t.reason) lines.push(` flagged: ${clip(t.reason)}`);
|
|
396
398
|
const held = leases.find((l) => String(l.task_id) === String(t.id) && !l.mine);
|
|
397
399
|
if (held) lines.push(` already being fixed by ${held.by} — leave it to that session`);
|
|
398
400
|
}
|
|
399
401
|
if (waiting.length) lines.push(' Waiting, not on you (set aside; they land on their own):');
|
|
400
|
-
for (const t of waiting) lines.push(` -
|
|
402
|
+
for (const t of waiting) lines.push(` - ${taskRef(t.id, t.short_name)}${t.title ? ` ${clip(t.title)}` : ''} — ${clip(t.waits_on || t.reason || 'outside you')}`);
|
|
401
403
|
lines.push(` ${clip(d.hint, 600) || 'Clear it with /strand-fix N, or rebase the branch and re-ship — or /builder-release it to give up on one.'}`);
|
|
402
404
|
return { lines, exit: QUEUE_GATED_EXIT };
|
|
403
405
|
}
|
|
@@ -118,6 +118,7 @@ function buildShippedEnvelope(crumb, task) {
|
|
|
118
118
|
...inputs,
|
|
119
119
|
postGradeStatus: 'shipped',
|
|
120
120
|
taskId: crumb.taskId,
|
|
121
|
+
shortName: inputs.shortName || (task && task.short_name),
|
|
121
122
|
// Prefer a fact from the fresh task row when the breadcrumb didn't carry it.
|
|
122
123
|
title: inputs.title || (task && task.title) || `Task #${crumb.taskId}`,
|
|
123
124
|
valueSummary: inputs.valueSummary || (task && task.value_summary) || '',
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// scripts/bongos/report-checkout.js — tell the server how far behind this computer's
|
|
3
|
+
// project folder is (task 1005021).
|
|
4
|
+
//
|
|
5
|
+
// WHY. Session start heals a stale main folder when it safely can; when it cannot, nobody
|
|
6
|
+
// would know — the owner's sat 1,000 changes behind for a week. This sends the facts to
|
|
7
|
+
// PATCH /me/checkout-freshness, and GET /me then tells the person and their agent.
|
|
8
|
+
// Spawned detached by .claude/hooks/session-start.js, so it never delays a session.
|
|
9
|
+
// Silent and fail-open: no session, offline, an older server — nothing happens.
|
|
10
|
+
//
|
|
11
|
+
// Usage: node scripts/bongos/report-checkout.js <behind> <branch> <reason>
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
const { apiCall, readSessionToken, cliExit } = require('./cli-lib');
|
|
16
|
+
|
|
17
|
+
async function main() {
|
|
18
|
+
const [behindRaw, branch, reason] = process.argv.slice(2);
|
|
19
|
+
const behind = Number.parseInt(behindRaw, 10);
|
|
20
|
+
if (!Number.isInteger(behind) || behind < 0 || !reason) return cliExit(0);
|
|
21
|
+
if (!readSessionToken()) return cliExit(0);
|
|
22
|
+
try {
|
|
23
|
+
await apiCall('PATCH', '/api/bongos/me/checkout-freshness',
|
|
24
|
+
{ behind, branch: branch && branch !== 'HEAD' ? branch : '', reason },
|
|
25
|
+
{ allowUnauthenticated: true });
|
|
26
|
+
} catch (_) { /* fail-open */ }
|
|
27
|
+
return cliExit(0);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (require.main === module) main();
|
|
@@ -266,6 +266,13 @@ const INTEGRATION = new Set([
|
|
|
266
266
|
// cannot be edited, deleted or truncated; unknown types and blank amounts are
|
|
267
267
|
// refused; the reads' SQL resolves a task's own set over the project's.
|
|
268
268
|
'pay_terms_db',
|
|
269
|
+
// task 1004991 (ADR 0385 pay-type gates): the gate table from the real migration —
|
|
270
|
+
// append-only, no eu_labour row, evidence required — and the verdict SQL.
|
|
271
|
+
'pay_type_gates_db',
|
|
272
|
+
// task 1004958: core_289's CHECK and the task-name backfill against real Postgres.
|
|
273
|
+
'task_name_db',
|
|
274
|
+
// task 1004958: the blocker read's lateral join that names held tasks, against real Postgres.
|
|
275
|
+
'blocker_tasks_named_db',
|
|
269
276
|
// task 1004988 (ADR 0385 D2): credit_log from the real migration — Postgres
|
|
270
277
|
// refuses a spend, redemption or transfer row; only a reversal of an existing
|
|
271
278
|
// row may be negative, and core_194's clawback still books.
|
|
@@ -274,6 +281,10 @@ const INTEGRATION = new Set([
|
|
|
274
281
|
// Postgres, the writer's ON CONFLICT turns it into a no-op, and a ledger already
|
|
275
282
|
// holding a duplicate still migrates (a warning, no index, nothing deleted).
|
|
276
283
|
'credit_log_idea_unique_db',
|
|
284
|
+
// task 1003182: the project-account record from the real migration — Postgres refuses a
|
|
285
|
+
// causeless or wrongly-directed movement, any UPDATE/DELETE/TRUNCATE, and a second row
|
|
286
|
+
// for one processor reference.
|
|
287
|
+
'project_account_db',
|
|
277
288
|
// task 1004408: migration platform_identity_028 deletes the old hub invite's
|
|
278
289
|
// pending rows before a cutoff it reads from the core_upgrades ledger. Only a real
|
|
279
290
|
// engine runs the DO block, the numeric version compare and the one-shot guard.
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
//
|
|
4
|
+
// scripts/bongos/sessions-board.js — every session a builder has going, and where they
|
|
5
|
+
// are jammed, as a board the AI shows in chat (task 1004957).
|
|
6
|
+
//
|
|
7
|
+
// WHY. A builder running several sessions at once could not see where things were stuck:
|
|
8
|
+
// which session was building, which piece was waiting to land, which had come back from
|
|
9
|
+
// review, which ship was stuck — and the one that matters most, that new work was paused
|
|
10
|
+
// for EVERY session because too many ships were stuck (the REBASE_REQUIRED queue gate).
|
|
11
|
+
// The owner wanted this as a board the AI shows in chat (/session-board), not a hall
|
|
12
|
+
// page, plus a one-line version at session start when something is jammed, so a new
|
|
13
|
+
// session knows not to pile on.
|
|
14
|
+
//
|
|
15
|
+
// WHERE IT READS. Nothing new on the server: GET /me already carries the builder's active
|
|
16
|
+
// claims (one per session, with the worktree it lives in) and the stuck ships that gate
|
|
17
|
+
// them (needs_rebase_tasks); the confirmed and completed task lists say what is waiting to
|
|
18
|
+
// land and what came back from review. Whether the gate is shut is the server's own rule
|
|
19
|
+
// (modules/lifecycle/rebase-gate.js), read here rather than re-derived. Who is fixing a
|
|
20
|
+
// stuck ship is the local strand lease (strand-lease.js, task 1004956).
|
|
21
|
+
//
|
|
22
|
+
// Usage:
|
|
23
|
+
// node scripts/bongos/sessions-board.js # the board, as markdown for chat
|
|
24
|
+
// node scripts/bongos/sessions-board.js --json # the same, as data
|
|
25
|
+
// node scripts/bongos/sessions-board.js --line # ONE line at session start, only when jammed
|
|
26
|
+
//
|
|
27
|
+
// The same line also rides strand-watch.js --line (the stuck-ship startup note) through
|
|
28
|
+
// lineNow(), which is how it reaches a new session: the owner chose that over adding a
|
|
29
|
+
// startup step of its own (2026-10-30).
|
|
30
|
+
//
|
|
31
|
+
// --line is FAIL-OPEN and SILENT when nothing is jammed, and carries ids and fixed words
|
|
32
|
+
// only — never a task title, which is builder-authored text injected into every session
|
|
33
|
+
// (the strand-watch.js trust boundary).
|
|
34
|
+
|
|
35
|
+
const { apiCall, hasFlag } = require('./cli-lib');
|
|
36
|
+
const { splitRebaseGate } = require('../../modules/lifecycle/rebase-gate.js');
|
|
37
|
+
// task 1004958: every task is written "task N - <name>"; safeTaskName passes only the
|
|
38
|
+
// generated name's fixed shape, so it is safe in the startup line too.
|
|
39
|
+
const { safeTaskName, taskRef } = require('../../modules/lifecycle/task-name.js');
|
|
40
|
+
|
|
41
|
+
const PAGE_LIMIT = 200;
|
|
42
|
+
const safeId = (v) => (/^\d{1,19}$/.test(String(v)) ? String(v) : null);
|
|
43
|
+
|
|
44
|
+
// A stuck ship's reason, in words a person reads.
|
|
45
|
+
const REASON_WORDS = Object.freeze({
|
|
46
|
+
land_not_proven: 'it may have landed, but that could not be proven',
|
|
47
|
+
pr_not_landed: 'its change is waiting to merge',
|
|
48
|
+
unresolvable_conflict: 'its change overlaps newer work',
|
|
49
|
+
branch_modifies_executed_code: 'it changes code the merge helper will not run; a person lands it',
|
|
50
|
+
red_check: 'a check failed on its change',
|
|
51
|
+
});
|
|
52
|
+
function reasonWords(reason) {
|
|
53
|
+
const key = String(reason || '').replace(/^strand:/, '').split(':')[0];
|
|
54
|
+
if (/^waiting:/.test(String(reason || ''))) return `waiting on ${String(reason).slice(8)}`;
|
|
55
|
+
return REASON_WORDS[key] || 'stuck on its way in';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function ageWords(iso, now) {
|
|
59
|
+
const t = Date.parse(iso || '');
|
|
60
|
+
if (!Number.isFinite(t)) return '';
|
|
61
|
+
const h = (now - t) / 3600000;
|
|
62
|
+
if (h < 1) return `${Math.max(1, Math.round(h * 60))}m`;
|
|
63
|
+
if (h < 48) return `${Math.round(h)}h`;
|
|
64
|
+
return `${Math.round(h / 24)}d`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const upper = (t) => t.charAt(0).toUpperCase() + t.slice(1);
|
|
68
|
+
|
|
69
|
+
// boardOf(input) → { sessions, landing, back, stuck, gate, jams }. PURE.
|
|
70
|
+
// me GET /me (active_claims, needs_rebase_tasks, builder.id)
|
|
71
|
+
// confirmed confirmed tasks (any builder; filtered to this one by shipped_by)
|
|
72
|
+
// completed completed tasks (same)
|
|
73
|
+
// leases live strand leases [{ task_id, holder, label, taken_at }]
|
|
74
|
+
// here this session's worktree { holder, label } or null
|
|
75
|
+
function boardOf({ me, confirmed = [], completed = [], leases = [], here = null, now = Date.now(), truncated = false } = {}) {
|
|
76
|
+
const meId = me && me.builder && me.builder.id != null ? String(me.builder.id) : null;
|
|
77
|
+
const claims = (me && Array.isArray(me.active_claims)) ? me.active_claims : [];
|
|
78
|
+
const owed = (me && Array.isArray(me.needs_rebase_tasks)) ? me.needs_rebase_tasks : [];
|
|
79
|
+
const mine = (t) => t && meId && String(t.shipped_by || t.claimed_by || '') === meId;
|
|
80
|
+
const stuckIds = new Set(owed.map((t) => String(t.id)));
|
|
81
|
+
const leaseFor = (id) => leases.find((l) => String(l.task_id) === String(id)) || null;
|
|
82
|
+
|
|
83
|
+
const sessions = claims.map((c) => ({
|
|
84
|
+
task_id: String(c.task_id),
|
|
85
|
+
name: safeTaskName(c.short_name),
|
|
86
|
+
title: c.title || null,
|
|
87
|
+
session: c.worktree_name || null,
|
|
88
|
+
here: !!(here && c.worktree_name && c.worktree_name === here.label),
|
|
89
|
+
stage: 'building',
|
|
90
|
+
since: ageWords(c.claimed_at, now),
|
|
91
|
+
waiting_on: c.release_requested_at ? 'someone asked for this task back' : null,
|
|
92
|
+
}));
|
|
93
|
+
const landing = confirmed.filter((t) => mine(t) && !stuckIds.has(String(t.id))).map((t) => ({
|
|
94
|
+
task_id: String(t.id), name: safeTaskName(t.short_name), title: t.title || null, stage: 'waiting to land', since: ageWords(t.updated_at, now),
|
|
95
|
+
}));
|
|
96
|
+
const back = completed.filter(mine).map((t) => ({
|
|
97
|
+
task_id: String(t.id), name: safeTaskName(t.short_name), title: t.title || null, stage: 'back from review', since: ageWords(t.updated_at, now),
|
|
98
|
+
waiting_on: 'fix what review found and ask again (or an artist approves a held page)',
|
|
99
|
+
}));
|
|
100
|
+
const split = splitRebaseGate(owed);
|
|
101
|
+
const stuck = owed.map((t) => {
|
|
102
|
+
const lease = leaseFor(t.id);
|
|
103
|
+
const waits = split.waiting.find((w) => String(w.id) === String(t.id));
|
|
104
|
+
return {
|
|
105
|
+
task_id: String(t.id),
|
|
106
|
+
name: safeTaskName(t.short_name),
|
|
107
|
+
title: t.title || null,
|
|
108
|
+
stage: 'stuck',
|
|
109
|
+
since: ageWords(t.needs_rebase_at, now),
|
|
110
|
+
why: reasonWords(t.needs_rebase_reason),
|
|
111
|
+
set_aside: !!waits,
|
|
112
|
+
fixer: lease ? (here && lease.holder === here.holder ? 'this session' : `session ${String(lease.label || '').replace(/[^\w.-]/g, '') || 'another session'}`) : null,
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
const gate = { shut: split.gates, fixable: split.fixable.length };
|
|
116
|
+
|
|
117
|
+
const jams = [];
|
|
118
|
+
if (gate.shut) {
|
|
119
|
+
jams.push({
|
|
120
|
+
kind: 'paused',
|
|
121
|
+
text: `New work is paused for every one of your sessions: ${split.fixable.length} stuck ship${split.fixable.length === 1 ? '' : 's'} must come down first.`,
|
|
122
|
+
task_ids: split.fixable.map((t) => String(t.id)),
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
for (const s of stuck) {
|
|
126
|
+
if (s.set_aside) continue;
|
|
127
|
+
jams.push({
|
|
128
|
+
kind: 'stuck',
|
|
129
|
+
text: s.fixer ? `${upper(taskRef(s.task_id, s.name))} is stuck (${s.why}); ${s.fixer} is fixing it.` : `${upper(taskRef(s.task_id, s.name))} is stuck (${s.why}); nobody is fixing it yet.`,
|
|
130
|
+
task_ids: [s.task_id],
|
|
131
|
+
fixer: s.fixer,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
// A full page of confirmed or completed tasks means older ones of this builder's may
|
|
135
|
+
// be past it: the board says so rather than passing a capped list off as complete.
|
|
136
|
+
return { sessions, landing, back, stuck, gate, jams, truncated: !!truncated };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// The board as markdown for chat: jams first, then one table.
|
|
140
|
+
function markdown(b) {
|
|
141
|
+
const out = [];
|
|
142
|
+
out.push('**Jams**');
|
|
143
|
+
if (!b.jams.length) out.push('- Nothing is jammed.');
|
|
144
|
+
for (const j of b.jams) out.push(`- ${j.text}`);
|
|
145
|
+
out.push('');
|
|
146
|
+
const rows = [
|
|
147
|
+
...b.sessions.map((s) => [s.session ? `${s.session}${s.here ? ' (this one)' : ''}` : '(no folder recorded)', s, s.waiting_on || '—']),
|
|
148
|
+
...b.stuck.map((s) => ['—', s, s.set_aside ? `${s.why} (set aside; it does not pause new work)` : s.fixer ? `${s.why}; ${s.fixer} is fixing it` : `${s.why}; nobody is fixing it`]),
|
|
149
|
+
...b.landing.map((s) => ['—', s, 'the merge; nothing for you to do']),
|
|
150
|
+
...b.back.map((s) => ['—', s, s.waiting_on]),
|
|
151
|
+
];
|
|
152
|
+
if (!rows.length) {
|
|
153
|
+
out.push('No sessions are building anything, and nothing of yours is in flight.');
|
|
154
|
+
return out.join('\n');
|
|
155
|
+
}
|
|
156
|
+
// A title is builder-authored: no link, emphasis, code or tag syntax reaches the chat,
|
|
157
|
+
// and a pipe cannot break the table.
|
|
158
|
+
const cell = (v) => String(v == null ? '' : v).replace(/[`*_[\]<>#~]/g, '').replace(/\|/g, '\\|').replace(/\s+/g, ' ').trim();
|
|
159
|
+
out.push('| Session | Task | Stage | Waiting on |');
|
|
160
|
+
out.push('|---|---|---|---|');
|
|
161
|
+
for (const [session, s, waits] of rows) {
|
|
162
|
+
const title = s.title ? ` — ${cell(s.title).slice(0, 70)}` : '';
|
|
163
|
+
out.push(`| ${cell(session)} | ${taskRef(s.task_id, s.name)}${title} | ${s.stage}${s.since ? ` (${s.since})` : ''} | ${cell(waits)} |`);
|
|
164
|
+
}
|
|
165
|
+
if (b.truncated) out.push('', 'More tasks were waiting to land or back from review than one read returns, so some older ones of yours may be missing from this board.');
|
|
166
|
+
return out.join('\n');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ONE line for session start, or null. Only the pause is worth interrupting a new session
|
|
170
|
+
// for — single stuck ships are strand-watch's line already. Ids, generated task names
|
|
171
|
+
// (safeTaskName) and fixed words only.
|
|
172
|
+
function line(b) {
|
|
173
|
+
if (!b || !b.gate.shut) return null;
|
|
174
|
+
const paused = b.jams.find((j) => j.kind === 'paused');
|
|
175
|
+
const ids = (paused ? paused.task_ids : []).map(safeId).filter(Boolean);
|
|
176
|
+
const nameOf = (id) => { const s = b.stuck.find((x) => x.task_id === id); return taskRef(id, s && s.name); };
|
|
177
|
+
const fixing = b.stuck.filter((s) => s.fixer && ids.includes(s.task_id)).map((s) => `${taskRef(s.task_id, s.name)} by ${s.fixer.replace(/[^\w .-]/g, '')}`);
|
|
178
|
+
const free = ids.filter((id) => !b.stuck.some((s) => s.task_id === id && s.fixer));
|
|
179
|
+
return `Sessions board: new work is paused for every session of this builder until ${ids.length} stuck ship${ids.length === 1 ? '' : 's'} (${ids.map(nameOf).join(', ')}) come down. `
|
|
180
|
+
+ (fixing.length ? `Already being fixed: ${fixing.join('; ')}. ` : '')
|
|
181
|
+
+ (free.length ? `Nobody is on ${free.map(nameOf).join(', ')} yet — take one with /strand-fix, or do unclaimed work. ` : 'Do not pile on: carry on with unclaimed work. ')
|
|
182
|
+
+ 'This note is for you, not the builder; show them /session-board only if they ask where things are stuck.';
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Who is fixing which stuck ship on this machine (strand-lease.js), and which session
|
|
186
|
+
// this is. No checkout: nobody is fixing anything that we can see.
|
|
187
|
+
function localLeases() {
|
|
188
|
+
try {
|
|
189
|
+
const lease = require('./strand-lease.js');
|
|
190
|
+
return { here: lease.currentHolder(), leases: lease.liveLeases({ dir: lease.leaseDir() }) };
|
|
191
|
+
} catch { return { here: null, leases: [] }; }
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function read() {
|
|
195
|
+
const body = (r) => (r && (r.data || r)) || {};
|
|
196
|
+
const [me, confirmed, completed] = await Promise.all([
|
|
197
|
+
apiCall('GET', '/api/bongos/me').then(body),
|
|
198
|
+
apiCall('GET', `/api/bongos/tasks?status=confirmed&limit=${PAGE_LIMIT}`).then((r) => body(r).tasks || []).catch(() => []),
|
|
199
|
+
apiCall('GET', `/api/bongos/tasks?status=completed&limit=${PAGE_LIMIT}`).then((r) => body(r).tasks || []).catch(() => []),
|
|
200
|
+
]);
|
|
201
|
+
const { leases, here } = localLeases();
|
|
202
|
+
const truncated = confirmed.length >= PAGE_LIMIT || completed.length >= PAGE_LIMIT;
|
|
203
|
+
return { me, confirmed, completed, leases, here, truncated };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// THE STARTUP READ, shared across session starts. The pause needs only the stuck ships
|
|
207
|
+
// on /me, so the line asks for nothing else, and the answer is cached for every session
|
|
208
|
+
// on the machine (probe-cache, the strand-watch line's own cache): four sessions opened
|
|
209
|
+
// together make ONE call, not four times three. Stuck ships are measured in hours, so a
|
|
210
|
+
// few minutes' age is as true as fresh. A failed read throws, so it is never cached.
|
|
211
|
+
const OWED_CACHE_KEY = 'sessions-board-owed';
|
|
212
|
+
const OWED_FRESH_MS = 5 * 60 * 1000;
|
|
213
|
+
async function readOwed() {
|
|
214
|
+
const { cached } = require('./probe-cache.js');
|
|
215
|
+
const { value } = await cached(OWED_CACHE_KEY, async () => {
|
|
216
|
+
const r = await apiCall('GET', '/api/bongos/me');
|
|
217
|
+
const me = (r && (r.data || r)) || {};
|
|
218
|
+
if (!me.builder || !Array.isArray(me.needs_rebase_tasks)) throw new Error('unreadable');
|
|
219
|
+
return { builder: { id: me.builder.id }, needs_rebase_tasks: me.needs_rebase_tasks };
|
|
220
|
+
}, { ttlMs: OWED_FRESH_MS });
|
|
221
|
+
const { leases, here } = localLeases();
|
|
222
|
+
return { me: value, leases, here };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// The startup line, read and bounded: the line, or null on silence, a slow read or any
|
|
226
|
+
// failure. Never throws.
|
|
227
|
+
async function lineNow({ deadlineMs = 4000 } = {}) {
|
|
228
|
+
try {
|
|
229
|
+
const deadline = new Promise((_, rej) => setTimeout(() => rej(new Error('deadline')), deadlineMs).unref());
|
|
230
|
+
return line(boardOf(await Promise.race([readOwed(), deadline])));
|
|
231
|
+
} catch { return null; }
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// The chat board as text. Fail OPEN: the board is read in chat, so a failed read is a
|
|
235
|
+
// sentence there, never a stack trace or a non-zero exit.
|
|
236
|
+
async function boardText({ json = false, readBoard = read } = {}) {
|
|
237
|
+
let input;
|
|
238
|
+
try {
|
|
239
|
+
input = await readBoard();
|
|
240
|
+
} catch (e) {
|
|
241
|
+
return json
|
|
242
|
+
? JSON.stringify({ available: false, reason: String((e && e.message) || e) }, null, 2)
|
|
243
|
+
: '**Jams**\n- The board could not be read just now (the server did not answer). Nothing is known to be jammed or clear; try again in a minute.';
|
|
244
|
+
}
|
|
245
|
+
const b = boardOf(input);
|
|
246
|
+
return json ? JSON.stringify(b, null, 2) : markdown(b);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async function main() {
|
|
250
|
+
if (hasFlag('--line')) {
|
|
251
|
+
const one = await lineNow();
|
|
252
|
+
if (one) console.log(one);
|
|
253
|
+
process.exitCode = 0;
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
console.log(await boardText({ json: hasFlag('--json') }));
|
|
257
|
+
process.exitCode = 0;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (require.main === module) {
|
|
261
|
+
main().catch((e) => { console.error('sessions-board: failed —', e.message || e); process.exitCode = 1; });
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
module.exports = { boardOf, markdown, boardText, line, lineNow, readOwed, reasonWords, OWED_CACHE_KEY };
|
|
@@ -344,6 +344,7 @@ async function printCompletionCard({ taskId, postGradeStatus, card, usage, land
|
|
|
344
344
|
stage, // task 1004379: 'held' from finishHeld; undefined ⇒ derived from postGradeStatus
|
|
345
345
|
postGradeStatus,
|
|
346
346
|
taskId,
|
|
347
|
+
shortName: card.shortName,
|
|
347
348
|
title: card.title,
|
|
348
349
|
valueSummary: card.valueSummary,
|
|
349
350
|
notes: card.notes,
|
|
@@ -442,6 +442,7 @@ async function shipMain() {
|
|
|
442
442
|
const model = shipCard.buildCardModel({
|
|
443
443
|
stage: 'review',
|
|
444
444
|
taskId,
|
|
445
|
+
shortName: ac.short_name,
|
|
445
446
|
title: ac.title,
|
|
446
447
|
valueSummary: summary,
|
|
447
448
|
fileCount: committedFiles.length || null,
|
|
@@ -780,6 +781,7 @@ async function shipMain() {
|
|
|
780
781
|
taskId, summary, advanceToMerge, skipMerge, postGradeStatus, doneLine: 'Done.', dbOnly, held,
|
|
781
782
|
// task 1270: data for the deterministic completion card.
|
|
782
783
|
card: {
|
|
784
|
+
shortName: ac.short_name,
|
|
783
785
|
title: ac.title,
|
|
784
786
|
valueSummary: summary,
|
|
785
787
|
notes,
|
|
@@ -968,7 +970,7 @@ async function regradeMain(opts = {}) {
|
|
|
968
970
|
await attachVisual();
|
|
969
971
|
await finishShip({
|
|
970
972
|
taskId, summary: summaryArg || task.value_summary || '', held: true, postGradeStatus: 'completed', doneLine: 'Done.',
|
|
971
|
-
card: { title: task.title, valueSummary: summaryArg || task.value_summary || '', notes: '' },
|
|
973
|
+
card: { shortName: task.short_name, title: task.title, valueSummary: summaryArg || task.value_summary || '', notes: '' },
|
|
972
974
|
});
|
|
973
975
|
return;
|
|
974
976
|
}
|
|
@@ -1088,7 +1090,7 @@ async function regradeMain(opts = {}) {
|
|
|
1088
1090
|
taskId, summary, advanceToMerge: confirmedStatus === 'confirmed', skipMerge, dbOnly: true,
|
|
1089
1091
|
postGradeStatus: confirmedStatus,
|
|
1090
1092
|
doneLine: 'Done — confirmed with no artifact (grader had nothing to read).',
|
|
1091
|
-
card: { title: task.title, valueSummary: summary, notes: '' },
|
|
1093
|
+
card: { shortName: task.short_name, title: task.title, valueSummary: summary, notes: '' },
|
|
1092
1094
|
});
|
|
1093
1095
|
return;
|
|
1094
1096
|
}
|
|
@@ -1190,6 +1192,7 @@ async function regradeMain(opts = {}) {
|
|
|
1190
1192
|
doneLine: g.passed ? 'Done — re-grade passed.' : 'Done — re-grade still failing; fix and try again.',
|
|
1191
1193
|
// task 1270: completion card for the re-grade path too.
|
|
1192
1194
|
card: {
|
|
1195
|
+
shortName: task.short_name,
|
|
1193
1196
|
title: task.title,
|
|
1194
1197
|
valueSummary: summary,
|
|
1195
1198
|
// Empty by design, not by omission (task 1003702). A re-grade takes no
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
// node scripts/bongos/strand-lease.js release <task-id> # drop this worktree's lease
|
|
36
36
|
// node scripts/bongos/strand-lease.js show [<task-id>] # who holds what
|
|
37
37
|
|
|
38
|
+
const { taskRef } = require('../../modules/lifecycle/task-name.js'); // task 1004958
|
|
38
39
|
const fs = require('node:fs');
|
|
39
40
|
const path = require('node:path');
|
|
40
41
|
|
|
@@ -197,11 +198,12 @@ function describe(lease, now = Date.now()) {
|
|
|
197
198
|
}
|
|
198
199
|
|
|
199
200
|
// The clause the strand note appends: the strands OTHER sessions hold, so this one leaves
|
|
200
|
-
// them alone. Null when nobody else holds one.
|
|
201
|
-
|
|
201
|
+
// them alone. Null when nobody else holds one. `names` maps a task id to its short name
|
|
202
|
+
// (task 1004958) — a lease is local and carries none, so the caller hands in what it knows.
|
|
203
|
+
function othersClause(leases, holder, now = Date.now(), names = {}) {
|
|
202
204
|
const others = (leases || []).filter((l) => l.holder !== holder);
|
|
203
205
|
if (!others.length) return null;
|
|
204
|
-
const list = others.map((l) =>
|
|
206
|
+
const list = others.map((l) => `${taskRef(safeId(l.task_id), names[String(l.task_id)])} by ${describe(l, now)}`).join('; ');
|
|
205
207
|
return `Already being fixed: ${list}. Leave ${others.length === 1 ? 'that one' : 'those'} to that session and carry on with your own work.`;
|
|
206
208
|
}
|
|
207
209
|
|