@bongos/core 1.19.1011 → 1.19.1013
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 +53 -43
- package/docs/adr/0331-the-sky-is-the-ideators-home.md +6 -1
- package/docs/copy-inventory.md +99 -56
- package/docs/copy-registry.json +830 -465
- package/docs/file-map.md +1 -1
- package/docs/module-api-changelog.md +4 -0
- package/modules/hall-ui/public/idea-objects.js +4 -1
- package/modules/hall-ui/public/sky-composer.js +155 -0
- package/modules/hall-ui/public/sky-data.js +287 -0
- package/modules/hall-ui/public/sky-draw.js +244 -0
- package/modules/hall-ui/public/sky-interaction.js +137 -0
- package/modules/hall-ui/public/sky-layout.js +81 -0
- package/modules/hall-ui/public/sky-panel.js +141 -0
- package/modules/hall-ui/public/sky-tour.js +80 -0
- package/modules/hall-ui/public/thinking.css +203 -267
- package/modules/hall-ui/public/thinking.html +119 -58
- package/modules/hall-ui/public/thinking.states.json +33 -9
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/gen-file-map.js +29 -12
- package/src/bongos/routes/me.js +4 -1
- package/src/bongos/serve-internal.js +20 -6
- package/src/module-api.js +1 -1
- package/tests/file_map.mjs +35 -3
- package/tests/hall_audit.mjs +27 -0
- package/tests/hall_page_gate_map.mjs +11 -5
- package/tests/hall_sky.mjs +393 -405
- package/tests/hall_thinking_room.mjs +274 -296
- package/tests/helpers.mjs +185 -217
- package/tests/ideator_large_dead_thought_proof.mjs +33 -25
- package/tests/ideator_traceable_proof.mjs +61 -39
- package/modules/hall-ui/public/sky.css +0 -365
- package/modules/hall-ui/public/sky.html +0 -79
- package/modules/hall-ui/public/sky.js +0 -580
- package/modules/hall-ui/public/sky.states.json +0 -20
- package/modules/hall-ui/public/thinking.js +0 -418
|
@@ -2,74 +2,135 @@
|
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
<title>Your
|
|
7
|
-
<meta name="description" content="
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
|
6
|
+
<title>Your sky — {{worldName}}</title>
|
|
7
|
+
<meta name="description" content="Your thinking, drawn from the ledger: every thought you filed as one of five objects, in the goal that caught it, sized by the work it caused.">
|
|
8
8
|
<link rel="icon" href="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><text x='16' y='24' font-size='24' text-anchor='middle' font-family='sans-serif'>⌂</text></svg>">
|
|
9
9
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
10
10
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
<!-- The faces (Manrope, JetBrains Mono) come from the branding pack's font
|
|
12
|
+
links, injected into this head by the server like every hall page's
|
|
13
|
+
(tests/hall_tokens.mjs: no hall page hardcodes a web-font stylesheet).
|
|
14
|
+
The template carried its own Google Fonts link for the same two faces. -->
|
|
15
|
+
<link rel="stylesheet" href="/builders/thinking.css?v=2026-09-25-sky3">
|
|
13
16
|
</head>
|
|
14
|
-
<!-- THE
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
<!-- THE SKY (task 1004231 / sky part 3 of 5, goal 1000095; ADR 0331, ADR 0227
|
|
18
|
+
§11). This page is docs/design/mocks/sky/sky.template.html — the prototype
|
|
19
|
+
the owner adopted as the design of record — built for real over GET /sky
|
|
20
|
+
and GET /sky/mine (part 2). The markup below is the template's, verbatim;
|
|
21
|
+
the sheet is its <style>; its one script is split into the seven sky-*.js
|
|
22
|
+
files under the 1,500-line ratchet (data → layout → draw → interaction →
|
|
23
|
+
panel → composer → tour). Every deviation a gate or a route forced is
|
|
24
|
+
listed in the shipping task's notes, one line each; anything else that
|
|
25
|
+
differs from the template is a defect.
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
is the least interesting thing on it (modules/hall-ui/CLAUDE.md, the
|
|
31
|
-
two page-head voices). The room is a working surface, not a document,
|
|
32
|
-
so it takes the board's head and not the reading room's. -->
|
|
33
|
-
<div class="page-head page-head--board">
|
|
34
|
-
<h1 class="page-head__h">Your thinking</h1>
|
|
35
|
-
<p class="page-head__sub" id="tr-sub">Loading what you filed…</p>
|
|
36
|
-
</div>
|
|
27
|
+
The file is still `thinking` and the URL still /thinking: this is the
|
|
28
|
+
ideator's home, the page that replaced THE ROOM (task 1004098) — and the
|
|
29
|
+
room survives inside it as the list drawer (L). /sky, the old ambient
|
|
30
|
+
page's URL, 301s here (src/bongos/serve-internal.js). It carries no hall
|
|
31
|
+
chrome on purpose: the sky is a full-screen surface with its own header,
|
|
32
|
+
and the hall's doors (the record, the goal, the task) open from the panel. -->
|
|
33
|
+
<body data-page="thinking" data-page-title="Your sky">
|
|
37
34
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
<a href="/builders/sky">your sky</a>.
|
|
46
|
-
</p>
|
|
35
|
+
<!-- The one <main> landmark the kit's a11y floor asks of every hall page
|
|
36
|
+
(one-header-main-h1). The template had none; the canvas is the page's
|
|
37
|
+
content, and the landmark wraps it with no rule of its own, so nothing
|
|
38
|
+
moves — the canvas is position: fixed. -->
|
|
39
|
+
<main>
|
|
40
|
+
<canvas id="sky" tabindex="0" role="img" aria-label="The sky, drawn from the ledger. Every thought and goal here is also a row in the list; press L to open it, ? to take the tour."></canvas>
|
|
41
|
+
</main>
|
|
47
42
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
43
|
+
<!-- The template wrote the whole top strip as the <header>. The kit's phone
|
|
44
|
+
floor (top-bar-80px) measures the page's <header>, and the strip wraps to
|
|
45
|
+
125px at 390 by the template's own rule — so the header is the BRAND (the
|
|
46
|
+
h1 and the figures) and the lens and tools stay its siblings inside the
|
|
47
|
+
same .top strip. No selector names the element, so the render is the
|
|
48
|
+
template's, pixel for pixel. -->
|
|
49
|
+
<div class="top">
|
|
50
|
+
<header class="brand">
|
|
51
|
+
<h1>your sky <em id="lensWord">— your thinking</em></h1>
|
|
52
|
+
<div class="stats" id="stats"></div>
|
|
53
|
+
</header>
|
|
54
|
+
<div class="lens" role="group" aria-label="Filter" id="lensGroup">
|
|
55
|
+
<button type="button" id="lensSimple" aria-pressed="false">simple</button>
|
|
56
|
+
<button type="button" id="lensMine" aria-pressed="true">your thinking</button>
|
|
57
|
+
<button type="button" id="lensAll" aria-pressed="false">the whole project</button>
|
|
58
|
+
</div>
|
|
59
|
+
<div class="tools" id="tools">
|
|
60
|
+
<button type="button" class="pill" id="roomBtn" aria-pressed="false" title="The same sky, as a list (L)">list</button>
|
|
61
|
+
<button type="button" class="pill" id="recentBtn" aria-pressed="false" hidden title="Only the thoughts of the last thirty days.">recent</button>
|
|
62
|
+
<button type="button" class="pill" id="claudeBtn" aria-pressed="false" title="Show the thoughts a session filed under your name. Off by default: only what a person filed.">claude layer</button>
|
|
63
|
+
<button type="button" class="pill" id="influenceBtn" aria-pressed="false" title="Only the goals your thoughts touched — everything else fades.">my influence</button>
|
|
64
|
+
<button type="button" class="pill" id="exploreBtn" aria-pressed="true" hidden title="Explore: the sky reveals itself as you follow routes from where you begin.">explore</button>
|
|
65
|
+
<button type="button" class="pill" id="ideasBtn" aria-pressed="false" hidden title="Lay your thoughts over the project map.">my ideas</button>
|
|
66
|
+
<button type="button" class="pill" id="rehearse" aria-pressed="false" title="What if: invents removals, dimming and nods — none has happened in the ledger yet — so you can see how the dead would read. Sizes are never invented.">what if</button>
|
|
67
|
+
<button type="button" class="pill icon" id="clockBtn" title="Show or hide the clock" aria-label="Show or hide the clock">◷</button>
|
|
68
|
+
<button type="button" class="pill icon" id="tourBtn" title="How to read the sky (?)" aria-label="How to read the sky">?</button>
|
|
69
|
+
<button type="button" class="pill icon" id="fitBtn" title="Fit the sky (0)" aria-label="Fit the whole sky">⌂</button>
|
|
60
70
|
</div>
|
|
61
71
|
</div>
|
|
62
72
|
|
|
63
|
-
<div
|
|
73
|
+
<div class="jump">
|
|
74
|
+
<input type="search" id="jump" placeholder="jump to a goal or a thought" autocomplete="off" aria-label="Jump to a goal or a thought">
|
|
75
|
+
<ul id="jumpList" hidden></ul>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<div class="composer" id="composerWrap">
|
|
79
|
+
<form id="file" autocomplete="off">
|
|
80
|
+
<input type="text" id="thought" placeholder="release a thought — one line" aria-label="Release a thought" maxlength="180">
|
|
81
|
+
<button type="button" class="mode" id="fullToggle" aria-pressed="false" title="File it as a Full Idea: the line, then five questions, scored on completeness. Off = a quick idea, one line.">full idea</button>
|
|
82
|
+
<button type="submit" class="go" id="fileGo">file</button>
|
|
83
|
+
</form>
|
|
84
|
+
<div class="gravity" id="gravity" aria-live="polite">gravity — the goal that catches it lights as you type; letting it float is a real answer</div>
|
|
85
|
+
<div class="steps" id="fullSteps" hidden></div>
|
|
86
|
+
<div class="receipt" id="receipt" hidden role="status"><span class="dot"></span><span id="receiptText"></span><button type="button" id="receiptGo">see it</button><button type="button" id="receiptX" aria-label="Dismiss">×</button></div>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<div class="card glass" id="card" role="tooltip" aria-hidden="true"></div>
|
|
90
|
+
<div class="menu glass" id="menu" hidden role="menu" aria-label="Chart the routes"></div>
|
|
91
|
+
|
|
92
|
+
<aside class="room" id="room" hidden aria-label="The list">
|
|
93
|
+
<div class="head"><h2 id="roomTitle">the room</h2><span class="lbl" id="roomCount"></span></div>
|
|
94
|
+
<div class="filters" id="filters" role="group" aria-label="Filter"></div>
|
|
95
|
+
<div class="rows" id="rows"></div>
|
|
96
|
+
</aside>
|
|
97
|
+
|
|
98
|
+
<aside class="panel" id="panel" hidden aria-live="polite"></aside>
|
|
99
|
+
|
|
100
|
+
<div class="start glass" id="start" hidden role="dialog" aria-label="Where do you begin?">
|
|
101
|
+
<span class="lbl">explore</span>
|
|
102
|
+
<h3>Where do you begin?</h3>
|
|
103
|
+
<p>The sky reveals itself as you go. Pick a well to start in; every route you chart brings the goal at its far end into view.</p>
|
|
104
|
+
<div class="opts" id="startOpts"></div>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<div class="tour glass" id="tour" hidden role="dialog" aria-label="How to read the sky">
|
|
108
|
+
<div class="step"><span class="lbl" id="tourN"></span><span class="dots" id="tourDots"></span></div>
|
|
109
|
+
<h3 id="tourH"></h3>
|
|
110
|
+
<p id="tourP"></p>
|
|
111
|
+
<div class="acts"><button type="button" class="door primary" id="tourNext">next</button><button type="button" class="door" id="tourBack">back</button><button type="button" class="skip" id="tourSkip">skip the tour</button></div>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<footer class="foot">
|
|
115
|
+
<div class="time" id="time">
|
|
116
|
+
<button type="button" class="play" id="play" aria-pressed="false" aria-label="Replay the sky from the first day">▶</button>
|
|
117
|
+
<input type="range" id="clock" min="0" max="1000" value="1000" aria-label="The day the sky is drawn for">
|
|
118
|
+
<span class="when" id="when">today</span>
|
|
119
|
+
<button type="button" class="hide" id="timeHide" aria-label="Hide the clock">×</button>
|
|
120
|
+
</div>
|
|
121
|
+
<div class="footrow">
|
|
122
|
+
<div class="legend" id="legend"></div>
|
|
123
|
+
<div class="alt" id="alt"></div>
|
|
124
|
+
</div>
|
|
125
|
+
</footer>
|
|
64
126
|
|
|
65
|
-
<script src="/builders/
|
|
66
|
-
<script src="/builders/
|
|
67
|
-
<script src="/builders/
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
<script src="/builders/
|
|
72
|
-
<script src="/builders/
|
|
73
|
-
<script src="/builders/thinking.js?v=2026-09-20-r03"></script>
|
|
127
|
+
<script src="/builders/idea-objects.js?v=2026-09-25-sky3"></script>
|
|
128
|
+
<script src="/builders/sky-data.js?v=2026-09-25-sky3"></script>
|
|
129
|
+
<script src="/builders/sky-layout.js?v=2026-09-25-sky3"></script>
|
|
130
|
+
<script src="/builders/sky-draw.js?v=2026-09-25-sky3"></script>
|
|
131
|
+
<script src="/builders/sky-interaction.js?v=2026-09-25-sky3"></script>
|
|
132
|
+
<script src="/builders/sky-panel.js?v=2026-09-25-sky3"></script>
|
|
133
|
+
<script src="/builders/sky-composer.js?v=2026-09-25-sky3"></script>
|
|
134
|
+
<script src="/builders/sky-tour.js?v=2026-09-25-sky3"></script>
|
|
74
135
|
</body>
|
|
75
136
|
</html>
|
|
@@ -1,20 +1,44 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_": "THE
|
|
2
|
+
"_": "THE SKY (task 1004231 / sky part 3 of 5, goal 1000095; ADR 0331) through modules/ui-design/kit/render.js. The page draws GET /sky + GET /sky/mine; against the kit's own stub neither exists, so every state below renders the EMPTY sky — a real state the floors must hold in (the composer, the tour, the list drawer and the lenses all exist with no thought filed). To look at the populated sky, run it against the hall-preview harness, whose sky.json + sky__mine.json fixtures carry the wells and one of each of the five objects:\n\n node scripts/hall-preview/server.js --port 4615 --fixture-me\n node modules/ui-design/kit/render.js --states modules/hall-ui/public/thinking.states.json --base-in http://localhost:4615 --base-out http://localhost:4615\n\nThe tour opens by itself on a first visit (sky-tour-seen), so states that want the bare sky skip it first.",
|
|
3
3
|
"page": "/builders/thinking",
|
|
4
4
|
"surface": "hall-ui",
|
|
5
5
|
"stub": { "prefix": "/builders/" },
|
|
6
|
+
"modes": ["dark"],
|
|
6
7
|
"modeQuery": false,
|
|
7
|
-
"ignoreRequests": ["/api/(gds|bongos)/"],
|
|
8
|
-
"ignoreConsole": ["/api/(gds|bongos)/", "\\b401\\b", "\\b404\\b", "Unauthorized", "Not Found", "load failed"],
|
|
8
|
+
"ignoreRequests": ["/api/(gds|bongos)/", "fonts\\.g(oogleapis|static)\\.com"],
|
|
9
|
+
"ignoreConsole": ["/api/(gds|bongos)/", "\\b401\\b", "\\b404\\b", "Unauthorized", "Not Found", "load failed", "fonts\\.g(oogleapis|static)\\.com"],
|
|
9
10
|
"states": {
|
|
10
|
-
"
|
|
11
|
-
"burning": {
|
|
11
|
+
"tour": {
|
|
12
12
|
"auth": true,
|
|
13
|
-
"_": "The
|
|
14
|
-
"actions": [["wait",
|
|
15
|
-
"expect": { "visible": ["#
|
|
13
|
+
"_": "The first visit: the sky with the tour's first step over it.",
|
|
14
|
+
"actions": [["wait", 1400]],
|
|
15
|
+
"expect": { "visible": ["#sky", "#tour"] }
|
|
16
|
+
},
|
|
17
|
+
"sky": {
|
|
18
|
+
"auth": true,
|
|
19
|
+
"_": "The 'your thinking' lens, bare: composer, lenses, tools, legend and the clock.",
|
|
20
|
+
"actions": [["wait", 1200], ["clickIfVisible", "#tourSkip"], ["wait", 300]],
|
|
21
|
+
"expect": { "visible": ["#sky", "#composerWrap"] }
|
|
22
|
+
},
|
|
23
|
+
"list": {
|
|
24
|
+
"auth": true,
|
|
25
|
+
"_": "The list drawer — the room — opened by the L key.",
|
|
26
|
+
"actions": [["wait", 1200], ["clickIfVisible", "#tourSkip"], ["wait", 200], ["press", "l"], ["wait", 300]],
|
|
27
|
+
"expect": { "visible": ["#room", "#rows"] }
|
|
28
|
+
},
|
|
29
|
+
"simple": {
|
|
30
|
+
"auth": true,
|
|
31
|
+
"_": "The simple lens: thoughts in their wells, recent / all, nothing else to press.",
|
|
32
|
+
"actions": [["wait", 1200], ["clickIfVisible", "#tourSkip"], ["wait", 200], ["click", "#lensSimple"], ["wait", 300]],
|
|
33
|
+
"expect": { "visible": ["#sky", "#recentBtn"] }
|
|
34
|
+
},
|
|
35
|
+
"full-idea": {
|
|
36
|
+
"auth": true,
|
|
37
|
+
"_": "The composer with a line typed: gravity says what would catch it. (The full-idea switch is hidden when the template read fails, as it does against the kit stub.)",
|
|
38
|
+
"actions": [["wait", 1200], ["clickIfVisible", "#tourSkip"], ["wait", 200], ["fill", "#thought", "a thought about the sky"], ["wait", 600]],
|
|
39
|
+
"expect": { "visible": ["#composerWrap", "#gravity"] }
|
|
16
40
|
},
|
|
17
41
|
"out": { "auth": false, "actions": [["wait", 600]] }
|
|
18
42
|
},
|
|
19
|
-
"reducedMotion": { "auth": true, "actions": [["wait",
|
|
43
|
+
"reducedMotion": { "auth": true, "actions": [["wait", 1200], ["clickIfVisible", "#tourSkip"], ["wait", 200]] }
|
|
20
44
|
}
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.1013",
|
|
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.1013",
|
|
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.1013",
|
|
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",
|
|
@@ -91,16 +91,31 @@ function materializedNames(absDir) { return new Set(Object.keys(readModuleSkills
|
|
|
91
91
|
// The subdirs of `relDir` holding at least one TRACKED file (task 1004213). An untracked one is the
|
|
92
92
|
// builder's own — a locally-installed plugin skill, or the empty husk a plugin extract leaves — and
|
|
93
93
|
// listing it would publish one machine's tooling into a shared doc on every post-merge regen.
|
|
94
|
-
// null when `root` is not the top of a git work tree (a packaged install, ADR 0108 §1)
|
|
94
|
+
// tracked: null when `root` is not the top of a git work tree (a packaged install, ADR 0108 §1).
|
|
95
|
+
// gitError: git COULD not answer (task 1004222) — a safe.directory refusal, git off PATH, a broken
|
|
96
|
+
// GIT_DIR, ENOBUFS. That also falls back to the disk listing, but loudly: main() warns and fails --check.
|
|
97
|
+
const GIT_MAX_BUFFER = 64 * 1024 * 1024;
|
|
98
|
+
// Discovery's own refusal. A broken GIT_DIR also exits 128 "not a git repository: '<dir>'", without this tail.
|
|
99
|
+
const NOT_A_REPO = /not a git repository \(or any (of the )?parent/;
|
|
95
100
|
function trackedSubdirs(root, relDir) {
|
|
96
|
-
|
|
101
|
+
// LC_ALL=C: the refusal is matched by its text, which a translated git would not print.
|
|
102
|
+
const git = (args) => execFileSync('git', args, {
|
|
103
|
+
cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], maxBuffer: GIT_MAX_BUFFER, env: { ...process.env, LC_ALL: 'C' },
|
|
104
|
+
});
|
|
97
105
|
try {
|
|
98
|
-
if (git(['rev-parse', '--show-prefix']).trim() !== '') return null;
|
|
99
|
-
return
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
106
|
+
if (git(['rev-parse', '--show-prefix']).trim() !== '') return { tracked: null, gitError: null };
|
|
107
|
+
return {
|
|
108
|
+
tracked: new Set(git(['ls-files', '-z', '--', relDir]).split('\0')
|
|
109
|
+
.map((p) => p.slice(relDir.length + 1).split('/'))
|
|
110
|
+
.filter((segs) => segs.length > 1)
|
|
111
|
+
.map((segs) => segs[0])),
|
|
112
|
+
gitError: null,
|
|
113
|
+
};
|
|
114
|
+
} catch (err) {
|
|
115
|
+
const stderr = String(err.stderr || '').trim();
|
|
116
|
+
if (err.status === 128 && NOT_A_REPO.test(stderr)) return { tracked: null, gitError: null };
|
|
117
|
+
return { tracked: null, gitError: stderr.split('\n')[0] || err.code || err.message };
|
|
118
|
+
}
|
|
104
119
|
}
|
|
105
120
|
|
|
106
121
|
// Immediate subdirectories of an absolute dir, sorted by name (deterministic); materialised
|
|
@@ -120,7 +135,7 @@ function listSubdirs(absDir) {
|
|
|
120
135
|
function buildSection(section, notes) {
|
|
121
136
|
const absDir = path.join(REPO_ROOT, section.dir);
|
|
122
137
|
const present = listSubdirs(absDir);
|
|
123
|
-
const tracked = trackedSubdirs(REPO_ROOT, section.dir);
|
|
138
|
+
const { tracked, gitError } = trackedSubdirs(REPO_ROOT, section.dir);
|
|
124
139
|
const names = tracked ? present.filter((n) => tracked.has(n)) : present;
|
|
125
140
|
const sectionNotes = (notes && notes[section.notesKey]) || {};
|
|
126
141
|
const entries = names.map((name) => {
|
|
@@ -135,7 +150,7 @@ function buildSection(section, notes) {
|
|
|
135
150
|
// A note is a ghost only when its dir is gone from disk — one not yet `git add`ed is not.
|
|
136
151
|
const onDisk = new Set(present);
|
|
137
152
|
const stale = Object.keys(sectionNotes).filter((k) => !onDisk.has(k)).map((k) => `${section.notesKey}.${k}`);
|
|
138
|
-
return { ...section, absDir, entries, missing, stale };
|
|
153
|
+
return { ...section, absDir, entries, missing, stale, gitError };
|
|
139
154
|
}
|
|
140
155
|
|
|
141
156
|
function buildModel() {
|
|
@@ -244,6 +259,7 @@ function computeWrites() {
|
|
|
244
259
|
const model = buildModel();
|
|
245
260
|
const missing = model.flatMap((m) => m.missing);
|
|
246
261
|
const stale = model.flatMap((m) => m.stale);
|
|
262
|
+
const gitErrors = [...new Set(model.map((m) => m.gitError).filter(Boolean))];
|
|
247
263
|
const writes = [];
|
|
248
264
|
let markersMissing = [];
|
|
249
265
|
let mapText = '';
|
|
@@ -258,7 +274,7 @@ function computeWrites() {
|
|
|
258
274
|
mapText = next; // coverage reads the POST-generation text, so a regen never changes the verdict
|
|
259
275
|
}
|
|
260
276
|
const modules = moduleCoverage(mapText);
|
|
261
|
-
return { model, writes, missing, stale, markersMissing, modules };
|
|
277
|
+
return { model, writes, missing, stale, gitErrors, markersMissing, modules };
|
|
262
278
|
}
|
|
263
279
|
|
|
264
280
|
function main() {
|
|
@@ -267,7 +283,7 @@ function main() {
|
|
|
267
283
|
process.stdout.write(JSON.stringify(buildModel(), null, 2) + '\n');
|
|
268
284
|
return;
|
|
269
285
|
}
|
|
270
|
-
const { writes, missing, stale, markersMissing, modules } = computeWrites();
|
|
286
|
+
const { writes, missing, stale, gitErrors, markersMissing, modules } = computeWrites();
|
|
271
287
|
|
|
272
288
|
// A missing note (dir without a note) or a stale note (note without a dir) is a hard failure in
|
|
273
289
|
// BOTH modes: in --check it fails CI; in write mode we still report it (and render a visible
|
|
@@ -275,6 +291,7 @@ function main() {
|
|
|
275
291
|
const problems = [];
|
|
276
292
|
if (missing.length) problems.push(`notes MISSING for: ${missing.join(', ')} — add a one-line note to docs/file-map.notes.json`);
|
|
277
293
|
if (stale.length) problems.push(`notes are STALE (no such dir) — remove from docs/file-map.notes.json: ${stale.join(', ')}`);
|
|
294
|
+
for (const e of gitErrors) problems.push(`git could not list tracked files (${e}) — every dir on disk was listed, untracked plugin installs included; fix git (PATH, safe.directory) and re-run (task 1004222)`);
|
|
278
295
|
if (markersMissing.length) problems.push(`docs/file-map.md is missing generated markers for: ${markersMissing.join(', ')}`);
|
|
279
296
|
if (modules.undocumented.length) problems.push(`modules with NO docs/file-map.md entry: ${modules.undocumented.map((k) => `${MODULES_DIR}/${k}`).join(', ')} — add an entry to the modules/ block of docs/file-map.md (task 1004066)`);
|
|
280
297
|
for (const d of modules.defects) problems.push(`module-coverage SCAN DEFECT — ${d}`);
|
package/src/bongos/routes/me.js
CHANGED
|
@@ -127,7 +127,10 @@ const discordIsLinked = (builderId) =>
|
|
|
127
127
|
// this projection omitted it. Adding an atom here is the deliberate act this comment
|
|
128
128
|
// already described; tests/nav_permission_atoms.mjs now fails if shell.js gates on
|
|
129
129
|
// one that is missing, so the act cannot be skipped again.
|
|
130
|
-
|
|
130
|
+
// `idea.triage` (task 1004231): the sky's panel draws "nod — make it claimable"
|
|
131
|
+
// on a thought awaiting a nod only for a builder who may triage ideas; the nod
|
|
132
|
+
// itself runs the existing POST /tasks/:id/promote, which stays the wall.
|
|
133
|
+
const NAV_PERMISSION_ATOMS = Object.freeze(['board.vote.cast', 'core.pin.move', 'idea.triage']);
|
|
131
134
|
|
|
132
135
|
// navPermissionsFor(builderId) → { '<atom>': boolean } over NAV_PERMISSION_ATOMS,
|
|
133
136
|
// or null when authority could not be established (government off, port missing,
|
|
@@ -1166,13 +1166,13 @@ function mountInternalSurfaces(app) {
|
|
|
1166
1166
|
// task 1003479 (goal 1000089): `goal-map` — the map of which goals wait on which,
|
|
1167
1167
|
// reached from Goals. It reads GET /goals/graph, which is `requireBuilder`, so a
|
|
1168
1168
|
// page gated any lower would show a signed-out visitor an empty shell.
|
|
1169
|
-
// task
|
|
1170
|
-
//
|
|
1171
|
-
//
|
|
1172
|
-
//
|
|
1173
|
-
//
|
|
1169
|
+
// task 1004231 (sky part 3): `thinking` is now THE SKY itself — the prototype
|
|
1170
|
+
// the owner adopted (docs/design/mocks/sky/, ADR 0331) built over GET /sky +
|
|
1171
|
+
// GET /sky/mine, with the room inside it as the list drawer. The old ambient
|
|
1172
|
+
// page at `sky` (task 1004105) is gone; its URL 301s to /thinking below, so it
|
|
1173
|
+
// leaves this regex — one URL cannot be two tiers, and a redirect is not a page.
|
|
1174
1174
|
const SIGNEDIN_BUILDERS_PAGE_RE =
|
|
1175
|
-
/^\/builders\/(?:work|settings|atlas|modules|goals|goal-map|roadmap|copy-desk|studio|ideas|thinking|
|
|
1175
|
+
/^\/builders\/(?:work|settings|atlas|modules|goals|goal-map|roadmap|copy-desk|studio|ideas|thinking|collab|task|idea|primer(?:\.md)?|diagrams)(?:\.html)?\/?$/;
|
|
1176
1176
|
// The diagram image assets under /builders/diagrams/ ride the same tier as the
|
|
1177
1177
|
// page that embeds them.
|
|
1178
1178
|
const SIGNEDIN_BUILDERS_PREFIX_RE = /^\/builders\/diagrams\//;
|
|
@@ -1227,6 +1227,20 @@ function mountInternalSurfaces(app) {
|
|
|
1227
1227
|
res.redirect(301, `${prefix}/work${qs}`);
|
|
1228
1228
|
});
|
|
1229
1229
|
|
|
1230
|
+
// task 1004231 (sky part 3 of 5, ADR 0331): the ideator's sky replaced BOTH
|
|
1231
|
+
// the room (/thinking) and the old ambient sky (/sky, task 1004105) with one
|
|
1232
|
+
// page at /thinking. The old URL survives as a 301 so a bookmark or the
|
|
1233
|
+
// room's old lede link lands on the sky — same shape as the /board → /work
|
|
1234
|
+
// redirect above: apex and subdomain forms, with or without .html, query
|
|
1235
|
+
// string preserved. The page gate never sees /sky: a redirect is not a page.
|
|
1236
|
+
app.get(/^(?:\/builders)?\/sky(?:\.html)?\/?$/, (req, res, next) => {
|
|
1237
|
+
if (isModuleSurfaceHost(req)) return next();
|
|
1238
|
+
const prefix = req.path.startsWith('/builders') ? '/builders' : '';
|
|
1239
|
+
const qi = req.url.indexOf('?');
|
|
1240
|
+
const qs = qi >= 0 ? req.url.slice(qi) : '';
|
|
1241
|
+
res.redirect(301, `${prefix}/thinking${qs}`);
|
|
1242
|
+
});
|
|
1243
|
+
|
|
1230
1244
|
// Onboarding diagram PNGs (V3.R76 #295) live in docs/onboarding/diagrams as the
|
|
1231
1245
|
// single source of truth alongside their Mermaid sources. Serve them here so the
|
|
1232
1246
|
// builders'-hall diagrams page can embed them without duplicating the binaries.
|
package/src/module-api.js
CHANGED
|
@@ -71,7 +71,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
|
|
|
71
71
|
// there. scripts/gds/bump-version.js still rewrites the literal below; it appends
|
|
72
72
|
// the entry to that file. Look for a version's history there, not here.
|
|
73
73
|
// ---------------------------------------------------------------------------
|
|
74
|
-
const CORE_VERSION = '1.19.
|
|
74
|
+
const CORE_VERSION = '1.19.1013'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
75
75
|
|
|
76
76
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
77
77
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
package/tests/file_map.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// (task 1276 / ADR 0066). Mirrors tests/session_index.mjs + tests/repo_map.mjs.
|
|
3
3
|
import assert from 'node:assert/strict';
|
|
4
4
|
import { test } from 'node:test';
|
|
5
|
-
import { execFileSync } from 'node:child_process';
|
|
5
|
+
import { execFileSync, spawnSync } from 'node:child_process';
|
|
6
6
|
import { readFileSync, existsSync, readdirSync, mkdirSync, writeFileSync, rmSync, mkdtempSync } from 'node:fs';
|
|
7
7
|
import { tmpdir } from 'node:os';
|
|
8
8
|
import { createRequire } from 'node:module';
|
|
@@ -90,10 +90,19 @@ function fixtureRoot({ git }) {
|
|
|
90
90
|
}
|
|
91
91
|
return root;
|
|
92
92
|
}
|
|
93
|
-
const skillsSectionAt = (cwd) => JSON.parse(execFileSync(process.execPath,
|
|
93
|
+
const skillsSectionAt = (cwd, env) => JSON.parse(execFileSync(process.execPath,
|
|
94
94
|
['-e', `process.stdout.write(JSON.stringify(require(${JSON.stringify(GEN)}).buildModel()))`],
|
|
95
|
-
{ cwd, encoding: 'utf8' })).find((s) => s.id === 'skills');
|
|
95
|
+
{ cwd, encoding: 'utf8', env })).find((s) => s.id === 'skills');
|
|
96
96
|
const skillNamesAt = (cwd) => skillsSectionAt(cwd).entries.map((e) => e.name);
|
|
97
|
+
function genAt(cwd, args, env) {
|
|
98
|
+
const r = spawnSync(process.execPath, [GEN, ...args], { cwd, encoding: 'utf8', env });
|
|
99
|
+
return { code: r.status, stderr: r.stderr };
|
|
100
|
+
}
|
|
101
|
+
// Two ways git fails to answer inside a real work tree (task 1004222): no git on PATH (ENOENT), and a
|
|
102
|
+
// GIT_DIR naming nothing — exit 128 like "not a repository", but not discovery's own refusal.
|
|
103
|
+
// PATH is dropped under every casing: Windows spells it `Path`, and a copy of both keys is ambiguous.
|
|
104
|
+
const withoutGit = (root) => ({ ...Object.fromEntries(Object.entries(process.env).filter(([k]) => k.toUpperCase() !== 'PATH')), PATH: path.join(root, 'no-bin') });
|
|
105
|
+
const brokenGitDir = (root) => ({ ...process.env, GIT_DIR: path.join(root, 'no-such-git-dir') });
|
|
97
106
|
|
|
98
107
|
test('--check FAILS (exit 2) when a new tracked skill has no note — a skill can no longer land undescribed', () => {
|
|
99
108
|
const root = fixtureRoot({ git: true });
|
|
@@ -128,15 +137,38 @@ test('outside a git work tree (a packaged install) every dir on disk is still li
|
|
|
128
137
|
const nested = path.join(parent, 'instance');
|
|
129
138
|
try {
|
|
130
139
|
assert.deepEqual(skillNamesAt(root), ['empty-husk', 'local-plugin', 'new-skill']);
|
|
140
|
+
// Silently: not being in a repository is the packaged install's normal state, not a git failure.
|
|
141
|
+
assert.equal(skillsSectionAt(root).gitError, null);
|
|
142
|
+
assert.doesNotMatch(genAt(root, ['--check']).stderr, /git could not/);
|
|
131
143
|
// A root that is a SUBDIR of some repo is not that repo's top: its files are not the repo's to judge.
|
|
132
144
|
mkdirSync(path.join(nested, '.claude', 'skills', 'inner'), { recursive: true });
|
|
133
145
|
assert.deepEqual(skillNamesAt(nested), ['inner']);
|
|
146
|
+
assert.equal(skillsSectionAt(nested).gitError, null);
|
|
134
147
|
} finally {
|
|
135
148
|
rmSync(root, { recursive: true, force: true });
|
|
136
149
|
rmSync(parent, { recursive: true, force: true });
|
|
137
150
|
}
|
|
138
151
|
});
|
|
139
152
|
|
|
153
|
+
test('a git failure inside a work tree is NOT "not a repository" — it warns in write mode and fails --check', () => {
|
|
154
|
+
const root = fixtureRoot({ git: true });
|
|
155
|
+
try {
|
|
156
|
+
for (const [shape, env, named] of [['git missing from PATH', withoutGit(root), /ENOENT/], ['GIT_DIR naming nothing', brokenGitDir(root), /not a git repository: /]]) {
|
|
157
|
+
assert.match(skillsSectionAt(root, env).gitError || '', named, `${shape}: the model names the git error`);
|
|
158
|
+
const check = genAt(root, ['--check'], env);
|
|
159
|
+
assert.equal(check.code, 2, `${shape}: --check must fail rather than pass on an unfiltered listing`);
|
|
160
|
+
assert.match(check.stderr, /gen-file-map --check: git could not list tracked files/, shape);
|
|
161
|
+
const write = genAt(root, [], env);
|
|
162
|
+
assert.equal(write.code, 2, `${shape}: write mode exits non-zero`);
|
|
163
|
+
assert.match(write.stderr, /gen-file-map: WARN git could not list tracked files/, shape);
|
|
164
|
+
}
|
|
165
|
+
// The same root with git answering carries no git error: the failure above is the env, not the fixture.
|
|
166
|
+
assert.equal(skillsSectionAt(root).gitError, null);
|
|
167
|
+
} finally {
|
|
168
|
+
rmSync(root, { recursive: true, force: true });
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
140
172
|
test('generation is deterministic + idempotent (a second run is a no-op)', () => {
|
|
141
173
|
const r1 = run([]);
|
|
142
174
|
assert.equal(r1.code, 0);
|
package/tests/hall_audit.mjs
CHANGED
|
@@ -354,9 +354,22 @@ test('the module doc still records where the 2179 density pass landed', () => {
|
|
|
354
354
|
// no shell.js, a private hardcoded palette that silently kept the WCAG-failing
|
|
355
355
|
// accent when the pack moved (idea 1000600). Task 1002868 adopted them, so the
|
|
356
356
|
// rule is now universal and exception-free.
|
|
357
|
+
// THE SKY is the one page that is not the hall's chrome, by the owner's decision
|
|
358
|
+
// (task 1004231, ADR 0331): modules/hall-ui/public/thinking.html is the
|
|
359
|
+
// prototype adopted as the design of record — a full-screen black surface with
|
|
360
|
+
// its own header, composer and legend — and its <style> is the pixel contract.
|
|
361
|
+
// It is not the fork this rule was written against: its palette is not a stale
|
|
362
|
+
// private copy of the hall's, it is a different surface's own, pinned by
|
|
363
|
+
// tests/hall_sky.mjs against the template. It still stamps the branding token
|
|
364
|
+
// and the pack's font links like every hall page (the server injects them).
|
|
365
|
+
const STANDALONE_EXEMPT = {
|
|
366
|
+
'thinking.html': 'the sky (task 1004231, ADR 0331) — the design of record is its own surface, not the hall chrome',
|
|
367
|
+
};
|
|
368
|
+
|
|
357
369
|
test('every hall page loads style.css and shell.js', () => {
|
|
358
370
|
const missing = [];
|
|
359
371
|
for (const f of pages()) {
|
|
372
|
+
if (STANDALONE_EXEMPT[f]) continue;
|
|
360
373
|
const html = read(f);
|
|
361
374
|
if (!/<link[^>]+href="[^"]*\bstyle\.css[^"]*"/.test(html)) missing.push(`${f}: no style.css link`);
|
|
362
375
|
if (!/<script[^>]+src="[^"]*\bshell\.js[^"]*"/.test(html)) missing.push(`${f}: no shell.js`);
|
|
@@ -400,6 +413,15 @@ test('no page declares a private copy of a pack color var', () => {
|
|
|
400
413
|
const LITERAL_EXEMPT = {
|
|
401
414
|
'style.css': 'the token tier — this is the ONE file whose job is to hold the literals',
|
|
402
415
|
'settings.css': 'the Discord button wears Discord\'s brand colour (#5865F2); a third-party mark is not a theme colour',
|
|
416
|
+
// The sky (task 1004231, ADR 0331) is the prototype the owner adopted as the
|
|
417
|
+
// design of record, verbatim: its sheet IS its token tier (a black void, one
|
|
418
|
+
// accent, the chart yellow, and the alpha-white ladder the canvas shares),
|
|
419
|
+
// and the two scripts that write inline colour do so with the template's own
|
|
420
|
+
// values. The atlas precedent: a page-scoped palette, pinned rather than
|
|
421
|
+
// re-tokened.
|
|
422
|
+
'thinking.css': 'the sky\'s own sheet is its token tier (task 1004231, ADR 0331 — the design of record)',
|
|
423
|
+
'sky-interaction.js': 'the sky\'s hover card writes the template\'s ring colour inline (task 1004231)',
|
|
424
|
+
'sky-panel.js': 'the sky\'s panel writes the template\'s dead-bar colour inline (task 1004231)',
|
|
403
425
|
};
|
|
404
426
|
const COLOUR_PROPS = 'color|background|background-color|border|border-color|border-top-color|border-bottom-color|fill|stroke|outline|outline-color|box-shadow|text-shadow';
|
|
405
427
|
const colourLiteralRe = () =>
|
|
@@ -465,6 +487,11 @@ test('the verdict classes and the scrim exist in the token tier, in both modes',
|
|
|
465
487
|
// exceptions are selectors that are not keyboard stops at all.
|
|
466
488
|
const OUTLINE_NONE_EXEMPT = {
|
|
467
489
|
main: 'the skip link\'s programmatic target — main takes tabindex="-1" focus so the reader lands past the nav, and ringing the whole page would be noise, not information',
|
|
490
|
+
// The sky's composer (task 1004231, ADR 0331): the line sits inside a pill
|
|
491
|
+
// form, and the indication is the FORM's ring — `.composer form:focus-within`
|
|
492
|
+
// brightens its inset hairline — which is the template's own design. A ring
|
|
493
|
+
// on the input inside the pill would draw two.
|
|
494
|
+
'.composer input': 'the sky\'s composer line — the indication is the pill form\'s focus-within ring (thinking.css), the template\'s own',
|
|
468
495
|
};
|
|
469
496
|
|
|
470
497
|
// Innermost rule blocks only, so a rule inside @media is read as itself and the
|
|
@@ -68,12 +68,15 @@ const ORACLE = {
|
|
|
68
68
|
// having a thought, and the page reads the VIEWER's own trail (the id comes
|
|
69
69
|
// from GET /me, never from the URL), so a rank could protect nothing the
|
|
70
70
|
// session does not already.
|
|
71
|
+
// Since task 1004231 (sky part 3, ADR 0331) that page IS the sky — the
|
|
72
|
+
// prototype the owner adopted, over GET /sky + GET /sky/mine, with the room
|
|
73
|
+
// inside it as the list drawer. Both reads are any-builder and the trail is
|
|
74
|
+
// the viewer's own, so sign-in is still the whole gate.
|
|
71
75
|
'/builders/thinking': 'requireBuilderPage',
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
'/builders/sky': 'requireBuilderPage',
|
|
76
|
+
// Gone: the old ambient sky (task 1004105) was folded into /thinking by task
|
|
77
|
+
// 1004231, and /builders/sky now 301s there (serve-internal.js). A redirect is
|
|
78
|
+
// not a page, so it carries no gate; GONE_PAGES keeps the row honest.
|
|
79
|
+
'/builders/sky': null,
|
|
77
80
|
'/builders/collab': 'requireBuilderPage',
|
|
78
81
|
// task 1002710: the orientation reading rooms moved DOWN to this tier from a
|
|
79
82
|
// thetes+ gate. They are the onboarding documents, so gating them above xenos
|
|
@@ -301,6 +304,9 @@ const GONE_PAGES = {
|
|
|
301
304
|
// Task 1003890 — the dev-box deprecation removed the fleet these two drove.
|
|
302
305
|
'/builders/harbor': 'task 1003890 — the dev box is gone, so its Archon view is too',
|
|
303
306
|
'/builders/pair': 'task 1003890 — the dev box is gone, so its pairing page is too',
|
|
307
|
+
// Task 1004231 — the ideator's sky replaced both the room and the old sky with
|
|
308
|
+
// one page at /thinking; /sky is a 301 to it, not a page.
|
|
309
|
+
'/builders/sky': 'task 1004231 — the sky lives at /thinking now; /sky redirects there',
|
|
304
310
|
};
|
|
305
311
|
|
|
306
312
|
test('a row that is null because the page left the hall serves no page', () => {
|