@bongos/core 1.19.645 → 1.19.646
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 +15 -15
- package/.claude/skills/grade-recover/SKILL.md +4 -2
- package/docs/module-api-changelog.md +2 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/claude-materialize.js +14 -1
- package/scripts/gds/ship-flow.js +27 -1
- package/scripts/gds/ship-honesty.js +53 -0
- package/src/module-api.js +1 -1
- package/tests/claude_materialize.mjs +30 -0
- package/tests/ship_cannot_lie.mjs +103 -1
package/.bongos-core.json
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
"artifact": "bongos-core",
|
|
3
3
|
"manifest_schema": 1,
|
|
4
4
|
"generator": "scripts/gds/package-core.js",
|
|
5
|
-
"core_version": "1.19.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.646",
|
|
6
|
+
"core_contract": "1.19.646",
|
|
7
|
+
"source_commit": "9648a16f755cae6136582c0d0ca4930a566a11af",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-10T18:
|
|
9
|
+
"built_at": "2026-09-10T18:10:26.956Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 470,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
19
|
"file_count": 2600,
|
|
20
|
-
"tree_sha256": "
|
|
20
|
+
"tree_sha256": "69d3719087a6b6e7fb7c1a0d4889c6bf272f0258a7d7cc72458179525775f6df",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/backlog-review/SKILL.md",
|
|
@@ -172,7 +172,7 @@
|
|
|
172
172
|
{
|
|
173
173
|
"path": ".claude/skills/grade-recover/SKILL.md",
|
|
174
174
|
"mode": "0000644",
|
|
175
|
-
"sha256": "
|
|
175
|
+
"sha256": "4dbbf04c19558b33332d9ba1bb8747198ded19e1a3af06ef72ede58ee4eb95ad"
|
|
176
176
|
},
|
|
177
177
|
{
|
|
178
178
|
"path": ".claude/skills/grader-health/SKILL.md",
|
|
@@ -2772,7 +2772,7 @@
|
|
|
2772
2772
|
{
|
|
2773
2773
|
"path": "docs/module-api-changelog.md",
|
|
2774
2774
|
"mode": "0000644",
|
|
2775
|
-
"sha256": "
|
|
2775
|
+
"sha256": "f4ceea78bdb8f6ba5fec64415aed58610a8ab011d3a8b550e517515904c8cfce"
|
|
2776
2776
|
},
|
|
2777
2777
|
{
|
|
2778
2778
|
"path": "docs/modules-contract.md",
|
|
@@ -7722,12 +7722,12 @@
|
|
|
7722
7722
|
{
|
|
7723
7723
|
"path": "package-lock.json",
|
|
7724
7724
|
"mode": "0000644",
|
|
7725
|
-
"sha256": "
|
|
7725
|
+
"sha256": "8def408bd91446b93b180b2696465b9bedaf1f8ebe15c671cd4f66a41f6b03da"
|
|
7726
7726
|
},
|
|
7727
7727
|
{
|
|
7728
7728
|
"path": "package.json",
|
|
7729
7729
|
"mode": "0000644",
|
|
7730
|
-
"sha256": "
|
|
7730
|
+
"sha256": "62ee3c0059ddc570ef2fef4f2288b7714d9eb95f64d390f67720eb7d07c92cd5"
|
|
7731
7731
|
},
|
|
7732
7732
|
{
|
|
7733
7733
|
"path": "public-docs/index.html",
|
|
@@ -7982,7 +7982,7 @@
|
|
|
7982
7982
|
{
|
|
7983
7983
|
"path": "scripts/gds/claude-materialize.js",
|
|
7984
7984
|
"mode": "0000644",
|
|
7985
|
-
"sha256": "
|
|
7985
|
+
"sha256": "cf333976f3c8fc9cfa1af57a121e5ee1f826d40fe7461bf1e32f939a59523663"
|
|
7986
7986
|
},
|
|
7987
7987
|
{
|
|
7988
7988
|
"path": "scripts/gds/claude-md-template.js",
|
|
@@ -8862,7 +8862,7 @@
|
|
|
8862
8862
|
{
|
|
8863
8863
|
"path": "scripts/gds/ship-flow.js",
|
|
8864
8864
|
"mode": "0000644",
|
|
8865
|
-
"sha256": "
|
|
8865
|
+
"sha256": "9853e719e4a695cc52ec9089298d05163210ed3b51ca9eceb7553bc0ed9c65bd"
|
|
8866
8866
|
},
|
|
8867
8867
|
{
|
|
8868
8868
|
"path": "scripts/gds/ship-git.js",
|
|
@@ -8882,7 +8882,7 @@
|
|
|
8882
8882
|
{
|
|
8883
8883
|
"path": "scripts/gds/ship-honesty.js",
|
|
8884
8884
|
"mode": "0000644",
|
|
8885
|
-
"sha256": "
|
|
8885
|
+
"sha256": "29116265032b48115c31df794aa4689e7ce3f5eaeef152ea1ca2390dc2078c39"
|
|
8886
8886
|
},
|
|
8887
8887
|
{
|
|
8888
8888
|
"path": "scripts/gds/ship-io.js",
|
|
@@ -9482,7 +9482,7 @@
|
|
|
9482
9482
|
{
|
|
9483
9483
|
"path": "src/module-api.js",
|
|
9484
9484
|
"mode": "0000644",
|
|
9485
|
-
"sha256": "
|
|
9485
|
+
"sha256": "bb3a06612cea7d25b04efabda8659ed8a71b66859bab279f9b4e8a1e5dbadff3"
|
|
9486
9486
|
},
|
|
9487
9487
|
{
|
|
9488
9488
|
"path": "src/module-loader/catalog.js",
|
|
@@ -10067,7 +10067,7 @@
|
|
|
10067
10067
|
{
|
|
10068
10068
|
"path": "tests/claude_materialize.mjs",
|
|
10069
10069
|
"mode": "0000644",
|
|
10070
|
-
"sha256": "
|
|
10070
|
+
"sha256": "b0474476af85716848114d345f4be67beb2e01ad3ceab36a25d474c3e9e536c5"
|
|
10071
10071
|
},
|
|
10072
10072
|
{
|
|
10073
10073
|
"path": "tests/clawback_confirmed_backlog.mjs",
|
|
@@ -12517,7 +12517,7 @@
|
|
|
12517
12517
|
{
|
|
12518
12518
|
"path": "tests/ship_cannot_lie.mjs",
|
|
12519
12519
|
"mode": "0000644",
|
|
12520
|
-
"sha256": "
|
|
12520
|
+
"sha256": "d8d99b6cd465b04eaf13790b0bdfe9a76ce36d77660407d3a2c6a17ac7852e8c"
|
|
12521
12521
|
},
|
|
12522
12522
|
{
|
|
12523
12523
|
"path": "tests/ship_card.mjs",
|
|
@@ -58,12 +58,14 @@ The panel ran and rejected the work. Recover by fixing, not by re-rolling:
|
|
|
58
58
|
1. **Group `issues[]` by severity, then by worker.** Nit/minor findings are advisory by rubric policy — they cannot have caused the fail on their own, so do not start there. The fail came from blocker/major findings (or a gating axis under threshold).
|
|
59
59
|
2. **For each blocker/major finding, add a checked-in `tests/*.mjs` case** that reproduces it, then fix until green. The grader treats throwaway verification as unverified — the committed test is what makes the fix real.
|
|
60
60
|
3. **Fix real findings once; do not chase re-worded restatements** of a point you already answered (answer it in the commit or with a tracked task ref instead).
|
|
61
|
-
4. Commit on the claim branch — the grader reads committed history — then:
|
|
61
|
+
4. Commit on the claim branch — the grader reads committed history — then re-grade, saying how each finding was answered:
|
|
62
62
|
|
|
63
63
|
```bash
|
|
64
|
-
node scripts/gds/ship.js <id> --regrade
|
|
64
|
+
node scripts/gds/ship.js <id> --regrade --response-file response.md
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
+
**`--response` is how you answer a finding; `--notes` is not** (task 1003702). The panel is given the task, your value summary, the changed files and the diff — it never reads ship notes, on a re-grade or on the first ship. `--response` rides to the panel fenced as the prior round's answer, so an already-answered finding cannot re-veto on re-wording alone. Passing `--notes` used to be accepted and silently dropped; it is now refused with this same guidance. Evidence itself still belongs in the **diff** — a committed test is what makes a fix real (step 2), and the panel can read every changed file.
|
|
68
|
+
|
|
67
69
|
### Not recoverable here — `REFUSED (authorization)`
|
|
68
70
|
|
|
69
71
|
A grade block naming protected surfaces above your rank is an authorization wall, not a quality verdict: no re-grade at your rank can land that diff as-is. Split the protected files out of the diff and re-ship the rest, or hand the task to a builder at/above the floor the refusal names.
|
|
@@ -1739,5 +1739,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
1739
1739
|
landed since 1.19.643 with no explicit bump. run 34509682128. (task 1002620)
|
|
1740
1740
|
1.19.645 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1741
1741
|
landed since 1.19.644 with no explicit bump. run 34511775606. (task 1002620)
|
|
1742
|
+
1.19.646 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1743
|
+
landed since 1.19.645 with no explicit bump. run 34512629504. (task 1002620)
|
|
1742
1744
|
---------------------------------------------------------------------------
|
|
1743
1745
|
```
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.646",
|
|
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.646",
|
|
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.646",
|
|
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",
|
|
@@ -431,7 +431,20 @@ function materializeClaude({ coreRoot, instanceDir, dryRun = false, overwrite =
|
|
|
431
431
|
landedNow[name] = { module: mod.key, source: `.claude/skills/${name}`, coreShipped: true };
|
|
432
432
|
}
|
|
433
433
|
}
|
|
434
|
-
|
|
434
|
+
// A name the CORE ships now is the core's, whatever an older manifest said — a core
|
|
435
|
+
// skill can take over a name a module once owned, and step 1 copies it in on this very
|
|
436
|
+
// run. Without this guard 1c deleted it again immediately (task 1003818, a regression
|
|
437
|
+
// in 1003632's own first cut). materializeModuleSkillsIntoCore states the same rule for
|
|
438
|
+
// the checkout side via its `tracked(name)` check; this is the instance-side twin.
|
|
439
|
+
//
|
|
440
|
+
// The `!skipSkills.has(name)` half is load-bearing, not belt-and-braces: when a DISABLED
|
|
441
|
+
// module declares a core-shipped name, step 1 SKIPS the copy (that is the task 2103b
|
|
442
|
+
// exclusion), so the instance's dir really is stale and must still be withdrawn. Keeping
|
|
443
|
+
// it on the strength of "the core ships this name" would resurrect exactly the bug 1c
|
|
444
|
+
// exists to fix. Deliberately asked of the CORE source tree rather than the roster, so a
|
|
445
|
+
// name no enabled module declares any more is still recognised as legitimately sourced.
|
|
446
|
+
const coreShipsNow = (name) => fs.existsSync(path.join(skillsSrc, name)) && !skipSkills.has(name);
|
|
447
|
+
const withdrawable = Object.keys(landedPreviously).filter((n) => SKILL_NAME_RE.test(n) && !landedNow[n] && !coreShipsNow(n));
|
|
435
448
|
if (!overwrite) {
|
|
436
449
|
// adopt mode never clobbers what the owner already has; withdrawing is a clobber.
|
|
437
450
|
if (withdrawable.length) summary.notes.push(`${withdrawable.length} skill(s) of a now-disabled module were left in place (adopt mode does not withdraw): ${withdrawable.sort().join(', ')}.`);
|
package/scripts/gds/ship-flow.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
'use strict';
|
|
10
10
|
|
|
11
11
|
const { cliClient, requireSession, arg, detectWorktreeName } = require('./cli-lib');
|
|
12
|
-
const { resolveProse, markShipProgress, smokeGateMessages } = require('./ship-honesty.js');
|
|
12
|
+
const { resolveProse, markShipProgress, smokeGateMessages, proseFlagPresent, regradeNotesRefusal } = require('./ship-honesty.js');
|
|
13
13
|
const { classifyEmptyShip } = require('../../modules/lifecycle/ship-preflight');
|
|
14
14
|
const { scopeForModule, isModuleKey } = require('../../src/bongos/module-scope-map');
|
|
15
15
|
const { matchOne } = require('../../src/bongos/path-match');
|
|
@@ -684,6 +684,16 @@ async function regradeMain() {
|
|
|
684
684
|
console.error('usage: node scripts/gds/ship.js <task-id> --regrade [--summary-file <path|-> | --summary "..."] [--response-file <path|-> | --response "..."] [--no-merge] [--force] [--verified] [--base <ref>]');
|
|
685
685
|
process.exit(2);
|
|
686
686
|
}
|
|
687
|
+
// task 1003702: refuse --notes rather than swallowing it. The reasoning — why
|
|
688
|
+
// a refusal and not a persist — is in ship-honesty.js above regradeNotesRefusal
|
|
689
|
+
// and is not repeated here. What is load-bearing at THIS call site is the
|
|
690
|
+
// POSITION: before the preflight and before any grade spend, because a refusal
|
|
691
|
+
// that first costs a 4-worker panel run is barely better than the silence it
|
|
692
|
+
// replaces. tests/ship_cannot_lie.mjs pins that order.
|
|
693
|
+
if (proseFlagPresent(args, 'notes')) {
|
|
694
|
+
for (const line of regradeNotesRefusal(taskId)) console.error(line);
|
|
695
|
+
process.exit(2);
|
|
696
|
+
}
|
|
687
697
|
const regradeProse = resolveProse(args, ['summary', 'response']);
|
|
688
698
|
const summaryArg = regradeProse.summary;
|
|
689
699
|
// task 1002662: the builder's response to the prior round's findings — how
|
|
@@ -753,6 +763,17 @@ async function regradeMain() {
|
|
|
753
763
|
// re-grade is the FIRST time this task actually ships. Fall back to the
|
|
754
764
|
// task's stored value_summary so the merge commit + Discord #ship-news post
|
|
755
765
|
// carry meaningful text even when --summary is omitted.
|
|
766
|
+
//
|
|
767
|
+
// AND IT NEVER REACHES THE TASK ROW — task 1003702's acceptance 3 asked this
|
|
768
|
+
// question; the answer is a real, separate defect, filed as task 1003817.
|
|
769
|
+
// `summary` from here feeds the grader prompt, the merge commit message and
|
|
770
|
+
// the PR title/body, and NOTHING writes it back to tasks.value_summary. All
|
|
771
|
+
// three candidate write paths were read and ruled out: publish-branch accepts
|
|
772
|
+
// value_summary but uses it only for PR text; PATCH /tasks/:id does not accept
|
|
773
|
+
// the field at all; the claim-resolve route is the sole writer and a re-grade
|
|
774
|
+
// has no claim (which is why claimLike is synthesized above). Fixing it needs
|
|
775
|
+
// a server route change, so it is deliberately NOT folded into this CLI fix.
|
|
776
|
+
// tests/ship_cannot_lie.mjs pins both halves of that so the answer cannot rot.
|
|
756
777
|
const summary = summaryArg || task.value_summary || '';
|
|
757
778
|
|
|
758
779
|
// The grader helpers want a claim-like object for touches[] (preflight's
|
|
@@ -848,6 +869,11 @@ async function regradeMain() {
|
|
|
848
869
|
card: {
|
|
849
870
|
title: task.title,
|
|
850
871
|
valueSummary: summary,
|
|
872
|
+
// Empty by design, not by omission (task 1003702). A re-grade takes no
|
|
873
|
+
// --notes at all now — it is refused above — so there are none from THIS
|
|
874
|
+
// round to show. The first ship's notes are the durable record and live on
|
|
875
|
+
// the resolved claim; re-printing them here would suggest this round
|
|
876
|
+
// produced them.
|
|
851
877
|
notes: '',
|
|
852
878
|
files: committedFiles,
|
|
853
879
|
baseline: graderBaseline,
|
|
@@ -143,8 +143,61 @@ function smokeGateMessages(taskId) {
|
|
|
143
143
|
};
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
// ---- --regrade must not swallow --notes (task 1003702) ----------------------
|
|
147
|
+
//
|
|
148
|
+
// THE THIRD WAY TO LOSE THE BUILDER'S PROSE, and the reason this lives in the
|
|
149
|
+
// prose-loss file rather than beside regradeMain. `--regrade` resolves only
|
|
150
|
+
// `summary` and `response`, so `--notes` parsed as an unknown flag, produced no
|
|
151
|
+
// error, and vanished. A builder answering a "no evidence in the notes" blocker
|
|
152
|
+
// passed it again in --notes, saw the ship run, and got the identical finding
|
|
153
|
+
// back marked unanswered — with nothing anywhere saying why.
|
|
154
|
+
//
|
|
155
|
+
// AND THE FIX IS A REFUSAL, NOT A PERSIST, because the premise the bug was filed
|
|
156
|
+
// under is wrong in the builder's favour: the panel NEVER reads ship notes, on a
|
|
157
|
+
// first ship or a re-grade. modules/grading/grader-prompt.js buildPrompt() puts
|
|
158
|
+
// the task row, the VALUE SUMMARY, the changed files, the diffstat and the diff
|
|
159
|
+
// in the prompt and nothing else, and the workers get the same bundle. So
|
|
160
|
+
// storing the notes would have fixed the silence and none of the substance: the
|
|
161
|
+
// builder would pass --notes, watch it save, and STILL get "unanswered" — a
|
|
162
|
+
// worse failure, because now it looks like it worked.
|
|
163
|
+
//
|
|
164
|
+
// The two channels that do reach the panel are named in the refusal: --response
|
|
165
|
+
// (fenced into the prior-round block, task 1002662) and the DIFF itself, which
|
|
166
|
+
// is why the workaround that actually worked on task 1003698 was committing the
|
|
167
|
+
// evidence as a file. Nothing is written and nothing is lost: the first ship's
|
|
168
|
+
// notes stay the durable ledger record, which is the one thing --notes on a
|
|
169
|
+
// re-grade could legitimately have meant.
|
|
170
|
+
function proseFlagPresent(args, name) {
|
|
171
|
+
const list = Array.isArray(args) ? args : [];
|
|
172
|
+
return list.some((a) => a === `--${name}` || String(a).startsWith(`--${name}=`)
|
|
173
|
+
|| a === `--${name}-file` || String(a).startsWith(`--${name}-file=`));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Pure so the refusal's WORDING is testable — a message that fails to name the
|
|
177
|
+
// alternative is the same dead end with an error code on it.
|
|
178
|
+
function regradeNotesRefusal(taskId) {
|
|
179
|
+
return [
|
|
180
|
+
'--regrade does not take --notes, and your notes have NOT been saved or graded.',
|
|
181
|
+
'',
|
|
182
|
+
'The grader never reads ship notes — not on a re-grade, and not on the first ship.',
|
|
183
|
+
'The panel is given the task, your VALUE SUMMARY, the changed files and the diff.',
|
|
184
|
+
'So notes cannot answer a finding, and silently keeping them would only look like it had.',
|
|
185
|
+
'',
|
|
186
|
+
'Put the evidence where the panel can actually see it:',
|
|
187
|
+
` --response "..." answers the prior round's findings; it rides to the panel fenced.`,
|
|
188
|
+
` --response-file <path> the safe form for prose (a shell eats backticks in an inline value).`,
|
|
189
|
+
' the DIFF commit the proof — a test, a dry-run log, a session-log file.',
|
|
190
|
+
' The panel reads the committed diff and can Read every changed file.',
|
|
191
|
+
'',
|
|
192
|
+
` node scripts/gds/ship.js ${taskId} --regrade --response-file notes.md`,
|
|
193
|
+
'',
|
|
194
|
+
"The first ship's notes stay the durable record; nothing you wrote earlier was lost.",
|
|
195
|
+
];
|
|
196
|
+
}
|
|
197
|
+
|
|
146
198
|
module.exports = {
|
|
147
199
|
resolveProse, markShipProgress, fatalRecoveryLines, smokeGateMessages,
|
|
200
|
+
proseFlagPresent, regradeNotesRefusal,
|
|
148
201
|
_resetForTest: () => { shipProgress = { stage: 'preflight', taskId: null }; },
|
|
149
202
|
_peekForTest: () => shipProgress,
|
|
150
203
|
};
|
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.646'; // 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');
|
|
@@ -318,6 +318,36 @@ t('materializeClaude: a skill the module STOPPED declaring is withdrawn too', ()
|
|
|
318
318
|
for (const d of [core, inst]) rmSync(d, { recursive: true, force: true });
|
|
319
319
|
});
|
|
320
320
|
|
|
321
|
+
t('materializeClaude: a CORE skill that took over a module-owned name is NOT withdrawn — and a disabled module\'s core-shipped name still is (task 1003818)', () => {
|
|
322
|
+
// The regression this pins: step 1 copies the core's dir in, and 1c deleted it again in
|
|
323
|
+
// the SAME run, because no enabled module declared the name any more. The core-checkout
|
|
324
|
+
// twin has always guarded this via tracked() ("a core skill can take over a name a
|
|
325
|
+
// module once owned"); the instance side shipped without it in task 1003632.
|
|
326
|
+
const { core, inst, modDir } = withdrawFixture();
|
|
327
|
+
m.materializeClaude({ coreRoot: core, instanceDir: inst, dryRun: false }); // alpha lands, recorded
|
|
328
|
+
|
|
329
|
+
// the module stops declaring alpha; the core now ships its own
|
|
330
|
+
writeFileSync(join(modDir, 'module.json'), JSON.stringify({ key: 'foo', default: true, contributes: { skills: ['beta'] } }));
|
|
331
|
+
mkdirSync(join(core, '.claude', 'skills', 'alpha'), { recursive: true });
|
|
332
|
+
writeFileSync(join(core, '.claude', 'skills', 'alpha', 'SKILL.md'), '---\nname: alpha\n---\nCORE OWNS THIS NOW\n');
|
|
333
|
+
const kept = m.materializeClaude({ coreRoot: core, instanceDir: inst, dryRun: false });
|
|
334
|
+
assert.deepEqual(kept.withdrawnSkills, [], 'a name the core ships now is the core\'s, whatever the manifest said');
|
|
335
|
+
assert.match(readFileSync(join(inst, '.claude', 'skills', 'alpha', 'SKILL.md'), 'utf8'), /CORE OWNS THIS NOW/, 'and it is the core copy that survives');
|
|
336
|
+
|
|
337
|
+
// THE OTHER HALF, which the guard must not break: a DISABLED module declaring a
|
|
338
|
+
// core-shipped name means step 1 skips the copy, so the instance dir IS stale.
|
|
339
|
+
const inst2 = mkdtempSync(join(tmpdir(), 'wd-inst-'));
|
|
340
|
+
writeFileSync(join(modDir, 'module.json'), JSON.stringify({ key: 'foo', default: true, contributes: { skills: ['alpha', 'beta'] } }));
|
|
341
|
+
m.materializeClaude({ coreRoot: core, instanceDir: inst2, dryRun: false });
|
|
342
|
+
assert.ok(existsSync(join(inst2, '.claude', 'skills', 'alpha')), 'landed while enabled');
|
|
343
|
+
mkdirSync(join(inst2, 'config'), { recursive: true });
|
|
344
|
+
writeFileSync(join(inst2, 'config', 'modules.json'), JSON.stringify({ modules: { foo: false } }));
|
|
345
|
+
const gone = m.materializeClaude({ coreRoot: core, instanceDir: inst2, dryRun: false });
|
|
346
|
+
assert.ok(gone.withdrawnSkills.includes('alpha'), 'a disabled module\'s core-shipped name is still withdrawn — step 1 skipped it, so the dir is stale');
|
|
347
|
+
assert.ok(!existsSync(join(inst2, '.claude', 'skills', 'alpha')));
|
|
348
|
+
for (const d of [core, inst, inst2]) rmSync(d, { recursive: true, force: true });
|
|
349
|
+
});
|
|
350
|
+
|
|
321
351
|
t('materializeClaude: a HAND-WRITTEN skill is never withdrawn, even when a disabled module declares its name', () => {
|
|
322
352
|
const { core, inst } = withdrawFixture();
|
|
323
353
|
// The owner wrote .claude/skills/alpha themselves; no materialize ever landed it,
|
|
@@ -14,13 +14,20 @@
|
|
|
14
14
|
// lands the task at 'completed' before the grade runs, and 'completed' is
|
|
15
15
|
// ALSO what a grade-failed task looks like — so a bare `fatal: <stack>`
|
|
16
16
|
// left nobody able to tell how far it got.
|
|
17
|
+
//
|
|
18
|
+
// (3) --regrade must not swallow --notes. Same shape as (1): prose handed to
|
|
19
|
+
// ship.js that reaches nothing, silently (task 1003702).
|
|
17
20
|
import { strict as assert } from 'node:assert';
|
|
18
21
|
import { test } from 'node:test';
|
|
19
22
|
import { createRequire } from 'node:module';
|
|
23
|
+
import { readFileSync } from 'node:fs';
|
|
20
24
|
|
|
21
25
|
const require = createRequire(import.meta.url);
|
|
22
26
|
const { argText } = require('../scripts/gds/cli-lib.js');
|
|
23
|
-
const {
|
|
27
|
+
const {
|
|
28
|
+
fatalRecoveryLines, markShipProgress, _peekForTest,
|
|
29
|
+
proseFlagPresent, regradeNotesRefusal,
|
|
30
|
+
} = require('../scripts/gds/ship-honesty.js');
|
|
24
31
|
|
|
25
32
|
// Injected IO so no case touches the filesystem.
|
|
26
33
|
const io = {
|
|
@@ -166,3 +173,98 @@ test('markShipProgress is the single writer, and ship.js re-exports the reader',
|
|
|
166
173
|
assert.equal(typeof shipCli.fatalRecoveryLines, 'function', 'ship.js still exposes it');
|
|
167
174
|
assert.equal(shipCli.fatalRecoveryLines, fatalRecoveryLines, 'same function, not a copy');
|
|
168
175
|
});
|
|
176
|
+
|
|
177
|
+
// ── (3) --regrade refuses --notes instead of swallowing it (task 1003702) ────
|
|
178
|
+
//
|
|
179
|
+
// The third way to lose the builder's prose, and the same incident shape as (1):
|
|
180
|
+
// prose handed to ship.js that reaches nothing, with no error. --regrade
|
|
181
|
+
// resolved only summary + response, so --notes parsed as an unknown flag and
|
|
182
|
+
// vanished; a builder answering a "no evidence in the notes" blocker passed it
|
|
183
|
+
// again there and got the identical finding back marked unanswered.
|
|
184
|
+
|
|
185
|
+
test('every spelling of --notes is caught, and nothing else is', () => {
|
|
186
|
+
// All four forms argText accepts. Catching only the bare flag would leave
|
|
187
|
+
// --notes-file — the form the ship docs actively recommend for prose — silent,
|
|
188
|
+
// which is the exact defect wearing different clothes.
|
|
189
|
+
for (const argv of [
|
|
190
|
+
['1', '--regrade', '--notes', 'x'],
|
|
191
|
+
['1', '--regrade', '--notes=x'],
|
|
192
|
+
['1', '--regrade', '--notes-file', 'n.md'],
|
|
193
|
+
['1', '--regrade', '--notes-file=n.md'],
|
|
194
|
+
]) {
|
|
195
|
+
assert.equal(proseFlagPresent(argv, 'notes'), true, `caught: ${argv.join(' ')}`);
|
|
196
|
+
}
|
|
197
|
+
// Not over-eager: the flags a re-grade legitimately takes must still pass, and
|
|
198
|
+
// a flag that merely CONTAINS the word is a different flag.
|
|
199
|
+
for (const argv of [
|
|
200
|
+
['1', '--regrade'],
|
|
201
|
+
['1', '--regrade', '--response-file', 'r.md'],
|
|
202
|
+
['1', '--regrade', '--summary', 'x'],
|
|
203
|
+
['1', '--regrade', '--no-notes-please'],
|
|
204
|
+
['1', '--regrade', '--response', 'my --notes were wrong'],
|
|
205
|
+
]) {
|
|
206
|
+
assert.equal(proseFlagPresent(argv, 'notes'), false, `not caught: ${argv.join(' ')}`);
|
|
207
|
+
}
|
|
208
|
+
// Generic over the flag name — it is not a --notes special case.
|
|
209
|
+
assert.equal(proseFlagPresent(['--response-file', 'r.md'], 'response'), true);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test('THE ACCEPTANCE: the refusal names where the evidence must go instead', () => {
|
|
213
|
+
// A non-zero exit that does not say what to do is the same dead end with an
|
|
214
|
+
// error code on it. The refusal has to carry the working channel, or a builder
|
|
215
|
+
// reads "not supported" and goes back to putting evidence nowhere.
|
|
216
|
+
const text = regradeNotesRefusal(1003698).join('\n');
|
|
217
|
+
assert.match(text, /--response/, 'names the channel that reaches the panel');
|
|
218
|
+
assert.match(text, /--response-file/, 'and its file form, the safe one for prose');
|
|
219
|
+
assert.match(text, /diff/i, 'and the diff, which is what the panel actually reads');
|
|
220
|
+
assert.match(text, /ship\.js 1003698 --regrade/, 'a runnable command with the real task id');
|
|
221
|
+
// It must not be reassuring about the thing that did not happen.
|
|
222
|
+
assert.match(text, /NOT been saved/, 'says plainly that the notes went nowhere');
|
|
223
|
+
// And it must not claim the notes were merely "not supported yet" — the
|
|
224
|
+
// substantive fact is that the grader never reads ship notes at all, so a
|
|
225
|
+
// builder does not go looking for a flag that would make them count.
|
|
226
|
+
assert.match(text, /never reads ship notes/, 'explains WHY, not just that it refused');
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test('the refusal is wired into the regrade path, before any grade spend', () => {
|
|
230
|
+
// Wiring proof (the (1) lesson: a helper nothing calls is not a fix). Also
|
|
231
|
+
// pins the ORDER — a refusal that costs a 4-worker panel run first is barely
|
|
232
|
+
// better than the silence, so the guard must precede the preflight.
|
|
233
|
+
const src = readFileSync(new URL('../scripts/gds/ship-flow.js', import.meta.url), 'utf8');
|
|
234
|
+
const regrade = src.slice(src.indexOf('async function regradeMain'));
|
|
235
|
+
const guard = regrade.indexOf("proseFlagPresent(args, 'notes')");
|
|
236
|
+
assert.ok(guard !== -1, 'regradeMain consults the guard');
|
|
237
|
+
const preflight = regrade.indexOf('preflight(claimLike');
|
|
238
|
+
assert.ok(preflight !== -1 && guard < preflight, 'it refuses before the preflight and the panel');
|
|
239
|
+
// The usage line must not advertise a flag the path refuses.
|
|
240
|
+
const usage = regrade.slice(0, regrade.indexOf('resolveProse'));
|
|
241
|
+
assert.ok(!/--notes/.test(usage.replace(/proseFlagPresent[^\n]*/g, '')
|
|
242
|
+
.replace(/\/\/[^\n]*/g, '')), 'usage does not offer --notes');
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test('ACCEPTANCE 3: --summary reaches the GRADER on a re-grade but never the task row', () => {
|
|
246
|
+
// Task 1003702's third acceptance asked whether --summary reaches the stored
|
|
247
|
+
// value_summary on a re-grade. It does NOT, and that is a separate defect
|
|
248
|
+
// filed as task 1003817 (it needs a server route change; this task's fix is
|
|
249
|
+
// CLI-only). Pinned here rather than left in prose for the reason this whole
|
|
250
|
+
// file exists: the panel reads the DIFF, so an answer that lives only in ship
|
|
251
|
+
// notes is an answer nobody can check — which is the very bug task 1003702 is.
|
|
252
|
+
const src = readFileSync(new URL('../scripts/gds/ship-flow.js', import.meta.url), 'utf8');
|
|
253
|
+
const regrade = src.slice(src.indexOf('async function regradeMain'));
|
|
254
|
+
const code = regrade.replace(/\/\/[^\n]*/g, ''); // comments describe, they do not write
|
|
255
|
+
|
|
256
|
+
// Half one — the threading is intact, so nothing regressed: --summary still
|
|
257
|
+
// falls back to the stored value and still reaches the grader and the card.
|
|
258
|
+
assert.match(code, /const summary = summaryArg \|\| task\.value_summary/,
|
|
259
|
+
'the stored summary is still the fallback when --summary is omitted');
|
|
260
|
+
assert.match(code, /gradeAndRecord\(\{[\s\S]{0,200}?summary,/,
|
|
261
|
+
'the summary still reaches the grader prompt');
|
|
262
|
+
assert.match(code, /valueSummary: summary/, 'and the completion card');
|
|
263
|
+
|
|
264
|
+
// Half two — and there is no writer. A `value_summary:` KEY would be a request
|
|
265
|
+
// payload; the legitimate read is `task.value_summary`, dot-access. If someone
|
|
266
|
+
// later adds the write (task 1003817), this assertion is what tells them to
|
|
267
|
+
// update the comment above `const summary` rather than leave it lying.
|
|
268
|
+
assert.ok(!/[^.]\bvalue_summary\s*:/.test(code),
|
|
269
|
+
'regradeMain writes value_summary nowhere — see task 1003817');
|
|
270
|
+
});
|