balladeer 1.0.0 → 1.0.2
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/README.md +53 -32
- package/dist/agent.d.ts +5 -0
- package/dist/agent.js +13 -1
- package/dist/cli.d.ts +16 -0
- package/dist/cli.js +181 -24
- package/dist/client.d.ts +24 -2
- package/dist/client.js +34 -3
- package/dist/commands/affected.js +8 -7
- package/dist/commands/check-seals.js +4 -4
- package/dist/commands/discover.js +16 -7
- package/dist/commands/explain.d.ts +1 -1
- package/dist/commands/explain.js +1 -1
- package/dist/commands/invite.js +2 -1
- package/dist/commands/prepare.d.ts +74 -0
- package/dist/commands/prepare.js +218 -0
- package/dist/commands/propose.d.ts +10 -0
- package/dist/commands/propose.js +29 -6
- package/dist/commands/repositories.js +1 -0
- package/dist/commands/session.d.ts +35 -0
- package/dist/commands/session.js +131 -0
- package/dist/commands/setup.d.ts +29 -0
- package/dist/commands/setup.js +302 -92
- package/dist/commands/status.d.ts +16 -0
- package/dist/commands/status.js +106 -23
- package/dist/commands/touch-map.js +2 -2
- package/dist/commands/whoami.js +2 -1
- package/dist/conventions.d.ts +9 -1
- package/dist/conventions.js +9 -1
- package/dist/copy.d.ts +83 -7
- package/dist/copy.js +226 -29
- package/dist/desktop-config.d.ts +85 -0
- package/dist/desktop-config.js +217 -0
- package/dist/git.d.ts +15 -0
- package/dist/git.js +23 -0
- package/dist/legacy.d.ts +41 -0
- package/dist/legacy.js +143 -0
- package/dist/local-time.d.ts +66 -0
- package/dist/local-time.js +84 -0
- package/dist/mcp-config.d.ts +10 -0
- package/dist/mcp-config.js +8 -4
- package/dist/session.d.ts +84 -0
- package/dist/session.js +135 -0
- package/dist/store.d.ts +11 -1
- package/dist/store.js +18 -6
- package/dist/wire.d.ts +95 -4
- package/dist/wire.js +2 -1
- package/package.json +1 -1
package/dist/commands/status.js
CHANGED
|
@@ -1,18 +1,52 @@
|
|
|
1
|
-
import { callAgentTool, noAgentCredentialSentence, selectAgent, structuredString, } from "../agent.js";
|
|
2
|
-
import { ClientTooOldError, RefusalError, TransportError, request } from "../client.js";
|
|
1
|
+
import { callAgentTool, noAgentCredentialSentence, reportAgentEnforcementWarning, selectAgent, structuredString, } from "../agent.js";
|
|
2
|
+
import { ClientTooOldError, RefusalError, TransportError, proposalReviewLink, reviewLink, request, } from "../client.js";
|
|
3
3
|
import { repositoryRoot } from "../git.js";
|
|
4
4
|
import { MARKER_OBSERVATION_LIMITS, observeMissingMarkers, staleMarkerRow, } from "../markers.js";
|
|
5
|
+
import { formatInstant } from "../local-time.js";
|
|
5
6
|
import { commandLine } from "../release.js";
|
|
6
7
|
import { repositoryHint } from "../repository.js";
|
|
7
8
|
import { SEALED_PATHS_SHOWN, sealedPromises } from "../seals.js";
|
|
8
9
|
import { StoreError, findSession, readCredentials } from "../store.js";
|
|
9
|
-
import {} from "../wire.js";
|
|
10
|
+
import { CLI_INVOCATION } from "../wire.js";
|
|
10
11
|
/**
|
|
11
12
|
* The bounded code for "this machine holds no credential for that repository".
|
|
12
13
|
* It is its own code rather than the session's: an agent branching on
|
|
13
14
|
* `session_expired` pairs again and still holds nothing.
|
|
14
15
|
*/
|
|
15
16
|
const NO_CREDENTIAL_HERE = "no_agent_credential_on_this_machine";
|
|
17
|
+
/**
|
|
18
|
+
* The two postures in which Balladeer is reporting the behavior as holding.
|
|
19
|
+
*
|
|
20
|
+
* Named as the small set rather than as "everything that is not broken",
|
|
21
|
+
* because the states this command actually meets are mostly neither. A promise
|
|
22
|
+
* whose meaning somebody agreed and nobody has checked, one whose required run
|
|
23
|
+
* never arrived, one whose newest evidence aged out, and one whose seal no
|
|
24
|
+
* longer matches all have no failing run behind them, and a reader who takes
|
|
25
|
+
* the absence of a failing run for a pass has been told the one thing this
|
|
26
|
+
* product refuses to say. The published contract this mirrors is `NOT_HOLDING`
|
|
27
|
+
* in `@balladeer/service` and the posture enum in `@balladeer/contracts`; this
|
|
28
|
+
* command carries its own copy because the published package depends on
|
|
29
|
+
* neither, and `tests/cli/promise-status-posture.test.ts` fails if the two
|
|
30
|
+
* drift apart.
|
|
31
|
+
*/
|
|
32
|
+
export const HOLDING_POSTURES = new Set(["protected", "qualifying"]);
|
|
33
|
+
/**
|
|
34
|
+
* Why a promise with no failing run behind it is still not holding.
|
|
35
|
+
*
|
|
36
|
+
* Two spellings of the broken seal, deliberately. `seal_broken` is what a read
|
|
37
|
+
* answers with; `custody_invalid` is what a Balladeer running behind this copy
|
|
38
|
+
* of the command still answers with, and a person talking to one of those is
|
|
39
|
+
* owed the sentence rather than the fallback.
|
|
40
|
+
*/
|
|
41
|
+
const POSTURE_REASONS = {
|
|
42
|
+
unknown: "No result could be trusted to say whether this holds.",
|
|
43
|
+
stale: "The bound verifier no longer matches the agreed meaning.",
|
|
44
|
+
seal_broken: "The last result could not be tied to the workflow this repository enrolled.",
|
|
45
|
+
custody_invalid: "The last result could not be tied to the workflow this repository enrolled.",
|
|
46
|
+
retired: "This promise was retired, and nothing checks it.",
|
|
47
|
+
};
|
|
48
|
+
/** What a posture this copy does not recognise says, rather than a green word. */
|
|
49
|
+
const UNREPORTED_POSTURE = "Balladeer is not reporting this behavior as holding, and this copy of the command does not recognise the state it is in.";
|
|
16
50
|
function noSessionSentence(controlPlane) {
|
|
17
51
|
return `Every repository in the workspace is read over a setup session, and none is stored for ${controlPlane}. Run \`${commandLine(null, "setup")}\` to read the whole workspace.`;
|
|
18
52
|
}
|
|
@@ -70,7 +104,23 @@ async function reportBalladeer(options) {
|
|
|
70
104
|
}
|
|
71
105
|
const here = options.repo ?? repositoryHint(options.cwd);
|
|
72
106
|
const selection = selectAgent(credentials.agents, options.controlPlane, options.repository, here);
|
|
73
|
-
const
|
|
107
|
+
const storedSession = findSession(credentials, options.controlPlane);
|
|
108
|
+
const repositoryScoped = options.repo !== undefined ||
|
|
109
|
+
options.repository !== undefined ||
|
|
110
|
+
here !== "unknown/unknown" ||
|
|
111
|
+
(await repositoryRoot(options.cwd)) !== undefined;
|
|
112
|
+
const hasBoundSession = typeof storedSession?.workspaceId === "string" && storedSession.workspaceId.trim().length > 0;
|
|
113
|
+
// A setup session belongs to one workspace, not to every repository whose
|
|
114
|
+
// agent happens to be stored on this machine. Establish that relationship
|
|
115
|
+
// before sending its bearer or fetching its workspace-wide context.
|
|
116
|
+
const session = hasBoundSession &&
|
|
117
|
+
(!repositoryScoped ||
|
|
118
|
+
(selection.kind === "agent" && selection.agent.workspaceId === storedSession.workspaceId))
|
|
119
|
+
? storedSession
|
|
120
|
+
: undefined;
|
|
121
|
+
const missingWorkspace = () => sayWorkspaceUnavailable(emit, say, storedSession === undefined ? "no_stored_setup_session" : "setup_session_scope_unverified", storedSession === undefined
|
|
122
|
+
? noSessionSentence(options.controlPlane)
|
|
123
|
+
: "An optional workspace view was skipped because the saved setup session could not be tied to this request. No workspace read was sent.");
|
|
74
124
|
// The credential this machine holds is what this repository is read over, so
|
|
75
125
|
// its absence is the first thing said, and it is said in its own words. What
|
|
76
126
|
// the founder got instead was the workspace half's refusal, "Balladeer refused
|
|
@@ -86,21 +136,13 @@ async function reportBalladeer(options) {
|
|
|
86
136
|
else {
|
|
87
137
|
options.write(`${message}\n`);
|
|
88
138
|
}
|
|
89
|
-
|
|
90
|
-
sayWorkspaceUnavailable(emit, say, "no_stored_setup_session", noSessionSentence(options.controlPlane));
|
|
91
|
-
}
|
|
92
|
-
else {
|
|
93
|
-
// A different question, over a different credential, so it is still
|
|
94
|
-
// answered underneath. Its outcome never becomes this run's exit code:
|
|
95
|
-
// this repository went unreported whatever the workspace read did.
|
|
96
|
-
await reportWorkspace(options, session, here, emit, say, true);
|
|
97
|
-
}
|
|
139
|
+
missingWorkspace();
|
|
98
140
|
return 4;
|
|
99
141
|
}
|
|
100
142
|
if (selection.kind === "refused" && session === undefined) {
|
|
101
143
|
// Nothing stored can answer anything, so this names the form that always
|
|
102
144
|
// works: the one this copy was run as.
|
|
103
|
-
return fail("nothing_stored", `${selection.reason} No Balladeer setup session is stored for ${options.controlPlane} either. Run: ${commandLine(null, "setup")}`, 4);
|
|
145
|
+
return fail("nothing_stored", `${selection.reason} ${storedSession === undefined ? `No Balladeer setup session is stored for ${options.controlPlane} either.` : "The stored setup session was not read because its workspace could not be established for this request."} Run: ${commandLine(null, "setup")}`, 4);
|
|
104
146
|
}
|
|
105
147
|
// One promise, by id: a different question, answered over the same connection
|
|
106
148
|
// and on its own. Somebody who pasted the repair line wants what the failing
|
|
@@ -116,9 +158,13 @@ async function reportBalladeer(options) {
|
|
|
116
158
|
let reported = false;
|
|
117
159
|
if (selection.kind === "agent") {
|
|
118
160
|
reported = await reportThisRepository(options, selection.agent, emit, say);
|
|
161
|
+
// A revoked or mismatched repository connection must not become a
|
|
162
|
+
// successful repository report through a different credential.
|
|
163
|
+
if (!reported && repositoryScoped)
|
|
164
|
+
return 4;
|
|
119
165
|
}
|
|
120
166
|
if (session === undefined) {
|
|
121
|
-
|
|
167
|
+
missingWorkspace();
|
|
122
168
|
return reported ? 0 : 4;
|
|
123
169
|
}
|
|
124
170
|
return reportWorkspace(options, session, here, emit, say, reported);
|
|
@@ -133,6 +179,7 @@ async function reportBalladeer(options) {
|
|
|
133
179
|
*/
|
|
134
180
|
async function reportThisRepository(options, agent, emit, say) {
|
|
135
181
|
const call = await callAgentTool(agent, "get_promise_setup", {});
|
|
182
|
+
reportAgentEnforcementWarning(call, options);
|
|
136
183
|
if (call.kind !== "result") {
|
|
137
184
|
say(`This repository could not be read over its Balladeer agent connection: ${reason(call)}`);
|
|
138
185
|
return false;
|
|
@@ -243,6 +290,7 @@ async function reportStaleMarkers(options, agent, emit, say) {
|
|
|
243
290
|
*/
|
|
244
291
|
async function reportOnePromise(options, promiseId, agent, emit, say) {
|
|
245
292
|
const call = await callAgentTool(agent, "get_promise", { promiseId });
|
|
293
|
+
reportAgentEnforcementWarning(call, options);
|
|
246
294
|
if (call.kind !== "result") {
|
|
247
295
|
const message = `${promiseId} could not be read over this repository's Balladeer agent connection: ${reason(call)}`;
|
|
248
296
|
emit({ step: "promise", status: "unreadable", promiseId, message });
|
|
@@ -252,7 +300,7 @@ async function reportOnePromise(options, promiseId, agent, emit, say) {
|
|
|
252
300
|
const structured = call.structured;
|
|
253
301
|
if (structured.found === false) {
|
|
254
302
|
const message = structuredString(call.structured, "reason") ??
|
|
255
|
-
`No
|
|
303
|
+
`No promise with the id ${promiseId} has meaning a named person has agreed in this repository, so there is nothing to read. Check the id with the person who gave it to you, or read what this repository has promised with list_promises.`;
|
|
256
304
|
emit({ step: "promise", status: "unreadable", promiseId, message });
|
|
257
305
|
say(message);
|
|
258
306
|
return 4;
|
|
@@ -270,11 +318,35 @@ async function reportOnePromise(options, promiseId, agent, emit, say) {
|
|
|
270
318
|
...(promiseUrl === undefined ? {} : { promiseUrl }),
|
|
271
319
|
};
|
|
272
320
|
if (threat === undefined) {
|
|
273
|
-
|
|
321
|
+
// Holding is read off the posture, never off the absence of a failing run.
|
|
322
|
+
// A promise nobody has built a check for has no failing run, and so does a
|
|
323
|
+
// promise whose required run never arrived, and answering "holding" to
|
|
324
|
+
// either one is the thing this product exists to refuse: agreed-but-
|
|
325
|
+
// unprotected meaning is context, and evidence that could not decide is
|
|
326
|
+
// Unknown. Both are green words away from anything green.
|
|
327
|
+
const holding = posture !== undefined && HOLDING_POSTURES.has(posture);
|
|
328
|
+
emit({ step: "promise", status: holding ? "holding" : "not_holding", ...named });
|
|
274
329
|
say(`${title ?? promiseId} (${promiseId})`);
|
|
275
330
|
if (posture !== undefined)
|
|
276
331
|
say(` Balladeer reports this promise as ${posture}.`);
|
|
277
|
-
|
|
332
|
+
// Agreed and nothing checking it is not a fault, so it is not reported as
|
|
333
|
+
// one. It is the one state where there is something to build, and the line
|
|
334
|
+
// that says what to run is the difference between a person reading
|
|
335
|
+
// "unqualified" and a person finishing the job.
|
|
336
|
+
if (posture === "unqualified") {
|
|
337
|
+
say(" Nobody has built a check for this promise yet, so nothing is proving it.");
|
|
338
|
+
say(` Prepare its qualification setup with: ${commandLine(null, "prepare")} ${promiseId}`);
|
|
339
|
+
}
|
|
340
|
+
else if (holding) {
|
|
341
|
+
say(" Nothing is reported broken here, so there is nothing to repair.");
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
// No run to read back, so none of the repair facts exist. What the person
|
|
345
|
+
// gets instead is why this is not holding and the page that carries the
|
|
346
|
+
// rest, rather than a sentence telling them nothing is wrong.
|
|
347
|
+
say(` ${POSTURE_REASONS[posture ?? ""] ?? UNREPORTED_POSTURE}`);
|
|
348
|
+
say(" No run is recorded against it here, so there is nothing to read back yet.");
|
|
349
|
+
}
|
|
278
350
|
if (promiseUrl !== undefined)
|
|
279
351
|
say(` Promise page: ${promiseUrl}`);
|
|
280
352
|
return 0;
|
|
@@ -310,7 +382,7 @@ async function reportOnePromise(options, promiseId, agent, emit, say) {
|
|
|
310
382
|
if (caseNote !== undefined)
|
|
311
383
|
say(` ${caseNote}`);
|
|
312
384
|
if (observedAt !== undefined)
|
|
313
|
-
say(` Observed: ${observedAt}`);
|
|
385
|
+
say(` Observed: ${formatInstant(observedAt)}`);
|
|
314
386
|
if (ownerName !== undefined)
|
|
315
387
|
say(` Owner: ${ownerName}`);
|
|
316
388
|
if (promiseUrl !== undefined)
|
|
@@ -351,7 +423,7 @@ async function reportSealedPaths(options, emit, say) {
|
|
|
351
423
|
say(` ${promise.sealedPath}${promise.title === undefined ? "" : ` ${promise.title}`}`);
|
|
352
424
|
if (sealed.length > shown.length)
|
|
353
425
|
say(` and ${sealed.length - shown.length} more, not listed here.`);
|
|
354
|
-
say(
|
|
426
|
+
say(` Run \`${CLI_INVOCATION} check-seals\` before you push to find out whether your change broke one.`);
|
|
355
427
|
}
|
|
356
428
|
function reason(call) {
|
|
357
429
|
switch (call.kind) {
|
|
@@ -418,6 +490,15 @@ async function reportWorkspace(options, session, here, emit, say, alreadyReporte
|
|
|
418
490
|
}
|
|
419
491
|
return failOutside(options, emit, code, "Balladeer could not report this workspace.", 5);
|
|
420
492
|
}
|
|
493
|
+
if (state?.workspace?.id !== session.workspaceId) {
|
|
494
|
+
sayWorkspaceUnavailable(emit, say, "setup_workspace_mismatch", "The setup session answered for a different workspace, so none of its workspace context was reported.");
|
|
495
|
+
return alreadyReported ? 0 : 4;
|
|
496
|
+
}
|
|
497
|
+
const scopedReviewLink = (url) => {
|
|
498
|
+
const scoped = new URL(url);
|
|
499
|
+
scoped.searchParams.set("workspaceId", state.workspace.id);
|
|
500
|
+
return scoped.toString();
|
|
501
|
+
};
|
|
421
502
|
const active = state.repositories.filter((repository) => repository.status === "active");
|
|
422
503
|
emit({
|
|
423
504
|
step: "receipt",
|
|
@@ -456,7 +537,9 @@ async function reportWorkspace(options, session, here, emit, say, alreadyReporte
|
|
|
456
537
|
say(` Default branch: ${repository.defaultBranch}`);
|
|
457
538
|
say(` Agent: ${repository.agentConfigured ? "connected" : "not connected"}`);
|
|
458
539
|
say(` CI: ${repository.ciConfigured
|
|
459
|
-
? `connected, ${repository.ciValidatedRunCount} authenticated run${repository.ciValidatedRunCount === 1 ? "" : "s"}, first observed ${repository.ciFirstValidatedAt
|
|
540
|
+
? `connected, ${repository.ciValidatedRunCount} authenticated run${repository.ciValidatedRunCount === 1 ? "" : "s"}, first observed ${repository.ciFirstValidatedAt === null
|
|
541
|
+
? "at an earlier run"
|
|
542
|
+
: formatInstant(repository.ciFirstValidatedAt)}`
|
|
460
543
|
: repository.ciIdentityRecorded
|
|
461
544
|
? "identity recorded, waiting for the first authenticated run"
|
|
462
545
|
: "not recorded"}`);
|
|
@@ -465,10 +548,10 @@ async function reportWorkspace(options, session, here, emit, say, alreadyReporte
|
|
|
465
548
|
// awaiting them, and a rejected proposal is listed nowhere at all.
|
|
466
549
|
say(` Promises: ${repository.promiseCount} agreed, ${repository.candidateCount} proposed and awaiting a person.`);
|
|
467
550
|
if (repository.firstPromiseId !== null) {
|
|
468
|
-
say(` First promise: ${options.controlPlane
|
|
551
|
+
say(` First promise: ${scopedReviewLink(reviewLink(options.controlPlane, `promises/${encodeURIComponent(repository.firstPromiseId)}`))}`);
|
|
469
552
|
}
|
|
470
553
|
if (repository.latestCandidateId !== null) {
|
|
471
|
-
say(` Newest proposal: ${options.controlPlane
|
|
554
|
+
say(` Newest proposal: ${scopedReviewLink(proposalReviewLink(options.controlPlane, repository.latestCandidateId))}`);
|
|
472
555
|
}
|
|
473
556
|
}
|
|
474
557
|
return 0;
|
|
@@ -4,7 +4,7 @@ import { dirname, join, resolve } from "node:path";
|
|
|
4
4
|
import { runCommand } from "../gh.js";
|
|
5
5
|
import { repositoryRoot } from "../git.js";
|
|
6
6
|
import { CONTINUITY_DIRECTORY, TOUCH_MAP_FILE, buildTouchMap, executedPaths, mappablePath, measurableWithNode, readCoverageDirectory, readLocalPackages, repositoryPath, serializeTouchMap, touchMapBody, touchMapEnvironment, verifierCwd, verifierDigest, } from "../touch-map.js";
|
|
7
|
-
import {} from "../wire.js";
|
|
7
|
+
import { CLI_INVOCATION } from "../wire.js";
|
|
8
8
|
/** A verifier gets this long before the measurement gives up on it. */
|
|
9
9
|
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
10
10
|
/** However long a package asks for, no verifier holds this command longer. */
|
|
@@ -214,7 +214,7 @@ export async function runTouchMap(options) {
|
|
|
214
214
|
say(` and ${map.unmapped.length - NAMED_LIMIT} more, not listed here.`);
|
|
215
215
|
}
|
|
216
216
|
say("");
|
|
217
|
-
say(
|
|
217
|
+
say(`Ask it which promises a change touches with: ${CLI_INVOCATION} affected <paths...>`);
|
|
218
218
|
return 0;
|
|
219
219
|
}
|
|
220
220
|
/**
|
package/dist/commands/whoami.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ClientTooOldError, RefusalError, TransportError, request } from "../client.js";
|
|
2
2
|
import { StoreError, dropSession, findSession, readCredentials, writeCredentials, } from "../store.js";
|
|
3
|
+
import { formatInstant } from "../local-time.js";
|
|
3
4
|
import { commandLine } from "../release.js";
|
|
4
5
|
import {} from "../wire.js";
|
|
5
6
|
function emit(options, step) {
|
|
@@ -48,7 +49,7 @@ export async function runWhoami(options) {
|
|
|
48
49
|
`Signed in as ${session.membershipDisplayName}, workspace "${session.workspaceName}" (${session.workspaceSlug}).`,
|
|
49
50
|
`Role: ${session.role}.`,
|
|
50
51
|
`This session may: ${session.scopeMeanings.join(", ")}.`,
|
|
51
|
-
`It expires at ${session.expiresAt}.`,
|
|
52
|
+
`It expires at ${formatInstant(session.expiresAt)}.`,
|
|
52
53
|
"",
|
|
53
54
|
].join("\n"));
|
|
54
55
|
}
|
package/dist/conventions.d.ts
CHANGED
|
@@ -18,8 +18,16 @@ export declare const CONVENTIONS_END = "<!-- balladeer:conventions:end -->";
|
|
|
18
18
|
/**
|
|
19
19
|
* The version of the text between the markers. It is written into the block and
|
|
20
20
|
* read back out of it, and nothing about finding the block depends on it.
|
|
21
|
+
*
|
|
22
|
+
* It moves when, and only when, `CONVENTIONS_BLOCK` changes. That block is the
|
|
23
|
+
* stable carrier: it lands in the customer's own instructions file at setup, and
|
|
24
|
+
* moving it means a pull request against every enrolled repository. Capture
|
|
25
|
+
* behavior is read every week and ships in `CAPTURE_BEHAVIOR`, which the server
|
|
26
|
+
* sends at the start of every session and which is versioned by the agent
|
|
27
|
+
* instructions marker instead. A bump here with no change to the block would
|
|
28
|
+
* rewrite twelve repositories to say exactly what they already said.
|
|
21
29
|
*/
|
|
22
|
-
export declare const CONVENTIONS_VERSION =
|
|
30
|
+
export declare const CONVENTIONS_VERSION = 16;
|
|
23
31
|
export declare function managedByLine(version: number): string;
|
|
24
32
|
/**
|
|
25
33
|
* Which version of the block a file already carries, or nothing when it carries
|
package/dist/conventions.js
CHANGED
|
@@ -22,8 +22,16 @@ export const CONVENTIONS_END = "<!-- balladeer:conventions:end -->";
|
|
|
22
22
|
/**
|
|
23
23
|
* The version of the text between the markers. It is written into the block and
|
|
24
24
|
* read back out of it, and nothing about finding the block depends on it.
|
|
25
|
+
*
|
|
26
|
+
* It moves when, and only when, `CONVENTIONS_BLOCK` changes. That block is the
|
|
27
|
+
* stable carrier: it lands in the customer's own instructions file at setup, and
|
|
28
|
+
* moving it means a pull request against every enrolled repository. Capture
|
|
29
|
+
* behavior is read every week and ships in `CAPTURE_BEHAVIOR`, which the server
|
|
30
|
+
* sends at the start of every session and which is versioned by the agent
|
|
31
|
+
* instructions marker instead. A bump here with no change to the block would
|
|
32
|
+
* rewrite twelve repositories to say exactly what they already said.
|
|
25
33
|
*/
|
|
26
|
-
export const CONVENTIONS_VERSION =
|
|
34
|
+
export const CONVENTIONS_VERSION = 16;
|
|
27
35
|
/** Both spellings: the stable marker, and the versioned one version 1 wrote. */
|
|
28
36
|
const START_MARKER = /<!-- balladeer:conventions:start(?: v(\d{1,4}))? -->/g;
|
|
29
37
|
const END_MARKER = /<!-- balladeer:conventions:end -->/g;
|
package/dist/copy.d.ts
CHANGED
|
@@ -7,7 +7,18 @@
|
|
|
7
7
|
* It describes the product's boundary, not one release's behaviour. What this
|
|
8
8
|
* release actually performs is stamped separately, outside this constant.
|
|
9
9
|
*/
|
|
10
|
-
export declare const BOUNDARY_COPY = "Balladeer never receives your code. It cannot read your repository: it holds no GitHub token,\ninstalls no GitHub App, and has no access to your files, tests, fixtures, logs, prompts, or\ntranscripts.\n\nWhat it does receive is small and bounded. From you: the text of each promise a named person on your\nteam approves, the numeric ids of your repository and its GitHub owner, and the name of its default\nbranch. From CI: GitHub's signed statement of which repository, workflow, and run is reporting, and\nfor each promise the run checked, the pass or fail outcome, the exact commit it checked, and content\nhashes of the verifier package and its output. Hashes cannot be turned back into your code or your\ntest output.\n\nThe verify job in your CI runs your tests with your checkout and has no Balladeer credential. A\nseparate publish job, with no checkout, sends only those outcomes and hashes.\n\
|
|
10
|
+
export declare const BOUNDARY_COPY = "Balladeer never receives your code. It cannot read your repository: it holds no GitHub token,\ninstalls no GitHub App, and has no access to your files, tests, fixtures, logs, prompts, or\ntranscripts.\n\nWhat it does receive is small and bounded. From you: the text of each promise a named person on your\nteam approves, the numeric ids of your repository and its GitHub owner, and the name of its default\nbranch. From CI: GitHub's signed statement of which repository, workflow, and run is reporting, and\nfor each promise the run checked, the pass or fail outcome, the exact commit it checked, and content\nhashes of the verifier package and its output. Hashes cannot be turned back into your code or your\ntest output.\n\nThe verify job in your CI runs your tests with your checkout and has no Balladeer credential. A\nseparate publish job, with no checkout, sends only those outcomes and hashes.\n\nBrowser approval creates a temporary setup session limited by that person's workspace role. A\ncontributor can connect this machine's coding agent to an already-enrolled repository and propose\npromises; an administrator can also add repositories, connect CI, and invite teammates. The session\ncannot approve, activate, rotate, revoke, or remove anything. It lasts a day, each use buys it\nanother day, and it is gone seven days after approval however much it was used. When setup connects\nan enrolled repository it also issues that machine a coding-agent credential. That connection has no\nexpiry: it lets the agent read the promises your team has approved, propose new ones, and propose\nreplacing, retiring, excepting, or reassigning one you already have; every one of those waits for a\nnamed person to decide it. It can also carry out one of those acts for you, and only this way: you\nopen a Balladeer page in your own browser, read what the act is, sign it off there, and read back\nthe one-time code that page gives you. That code covers that one act on that one thing, once, and\nBalladeer records you as the person who took it and the connection as the messenger. Without a code\nyou signed, the connection cannot approve, activate, grant, transfer, retire, or delete anything.\nYou can revoke either one at any time in Balladeer, and the setup session also ends on its own.\n\nAdding the workflow puts a check on pull requests into your default branch. That check is advisory\non Balladeer's side; whether it blocks a merge is your own branch protection.\n";
|
|
11
|
+
/**
|
|
12
|
+
* The one thing about approving that the terminal has to say.
|
|
13
|
+
*
|
|
14
|
+
* A person who belongs to two workspaces approves into whichever one their
|
|
15
|
+
* browser is signed into, and it is the browser that decides, not the terminal.
|
|
16
|
+
* Twice that was the wrong one: the repository was enrolled in a workspace
|
|
17
|
+
* nobody meant and an agent connection was minted there. The approval page now
|
|
18
|
+
* names the workspace above the button and offers the switch, and this line is
|
|
19
|
+
* what makes somebody look at it before they click.
|
|
20
|
+
*/
|
|
21
|
+
export declare const APPROVE_IN_THE_RIGHT_WORKSPACE = "Approve it while your browser is in the workspace you mean; the page names it.";
|
|
11
22
|
/**
|
|
12
23
|
* How a person gets a workspace to approve into, for an agent reading the
|
|
13
24
|
* setup instructions.
|
|
@@ -24,7 +35,7 @@ export declare const BOUNDARY_COPY = "Balladeer never receives your code. It can
|
|
|
24
35
|
* control plane, and a packaging test compares them: the page an agent fetches
|
|
25
36
|
* and the terminal it is reading cannot come to describe this differently.
|
|
26
37
|
*/
|
|
27
|
-
export declare const JOIN_OR_CREATE = "The pairing page joins an existing workspace or makes a new one. Somebody whose team already has\na Balladeer workspace
|
|
38
|
+
export declare const JOIN_OR_CREATE = "The pairing page joins an existing workspace or makes a new one. Somebody whose team already has\na Balladeer workspace accepts their email invitation, then runs `npx -y balladeer@latest setup --existing` in\nthe checkout they mean to use. That teammate mode never adds a repository or changes CI. After they\nsign in and approve the code, onboarding continues even when the checkout is not in the workspace.\nIf it is already enrolled, setup connects this machine's coding agent. If it is absent, the person\ncan still use Balladeer in the browser; a red warning explains that enforcement cannot be tracked\nuntil an administrator connects a repository.\n\nSomebody who is the first person from their team signs in, names a workspace, and clicks Create, and\nonly then is there anything to approve into.\n\nYou can save them the typing, and nothing else. Ask what the workspace should be called, then run\n`npx -y balladeer@latest setup --create-workspace \"<name>\"` with the name they gave you. The pairing link this\ncommand prints then carries `?create=<name>`, which pre-fills that name in the create form on the\npairing page. It fills the box and no more than that: the person signs in and clicks Create, and\nuntil they do there is no workspace. An agent never creates one, and Balladeer refuses the attempt\nwhatever it was told in conversation. Never invent the name either. A workspace is named once and\nthe whole team reads it.";
|
|
28
39
|
/**
|
|
29
40
|
* When a coding agent reads this repository's promises, and when it does not.
|
|
30
41
|
*
|
|
@@ -58,7 +69,53 @@ export declare const RETRIEVAL_SCOPE = "### Reading what is already agreed\n\nRe
|
|
|
58
69
|
* plane's `/agent` page carries the same bytes under its own constant, and a
|
|
59
70
|
* packaging test compares them.
|
|
60
71
|
*/
|
|
61
|
-
export declare const SESSION_BEHAVIORS = "###
|
|
72
|
+
export declare const SESSION_BEHAVIORS = "### Three things to offer without being asked\n\nAt the start of any session in this repository, before you plan anything, ask list_promises for the\npromises nothing is checking yet that belong to the person you are working with: `unverified` true\nand `mine` true, which is one bounded read rather than the catalog. If it returns any, say so in one\nline and offer to build their verifiers now. Name each one by its one-sentence claim rather than by\nan id, so the person can see which behavior is unguarded. Ask the same tool for\n`brokenSinceLastSeen` true as well, and where that returns any, say in one line that those promises\nbroke since they last looked, name each by its claim, and offer to fix them. Then wait for their\nanswer. The offer is the whole of it, and never start building or repairing one because nobody said\nno.\n\nTheirs, and nobody else's. `mine` keeps the promises this person owns or agreed to, and a teammate's\nunguarded promise is that teammate's to hear about: a session that opens by reading out other\npeople's unfinished work reads as an audit of them. `brokenSinceLastSeen` is that person's own by\nconstruction and needs no `mine` beside it. Drop `mine` when this person asks what the rest of the\nteam has promised, and say whose promises you are showing them when you do.\n\nWhen you have proposed promises, show them here as well as there. Put each proposal in the\nconversation in full: its one-sentence claim, who it is for, when it applies and what must then be\ntrue, the numbered cases that must keep working and the ones that must be caught, and every question\nyou left open. Then offer to finish the agreement from here, by asking for a sign-off, giving them\nthe page it returns and taking the one-time code that page shows them, so the browser is needed only\nfor signing. Give them the review link in the same message too, because some people would rather\nread it there and edit it before they agree.\nA promise whose meaning is agreed and which nothing is checking yet is one you can finish. When\nsomebody gives you a promise id, run `npx -y balladeer@latest status <promise id>`, or expand it with get_promise.\nIf it comes back agreed with nothing checking it, say so in one line and offer to prepare and build\nits verifier now. Only if they say yes: `prepare_qualification` mints the one-time setup, or\n`npx -y balladeer@latest prepare <promise id>` mints it and writes it where the sealed run reads it. You need no\nsign-off for that and there is no code to ask anybody for, because agreeing the meaning was their\nact and building the check that proves it is yours. Then build the verifier, seal it, push, and tell\nthem protection starts by itself when that run qualifies. The offer is the whole of it: never\nprepare one because nobody said no.";
|
|
73
|
+
/**
|
|
74
|
+
* The capture rules that do not change: when to say nothing, what a yes is
|
|
75
|
+
* worth, and the two promises Balladeer refuses.
|
|
76
|
+
*
|
|
77
|
+
* This block is written into the customer's own instructions file once, at
|
|
78
|
+
* setup, and is never refreshed afterwards. Changing it means opening a pull
|
|
79
|
+
* request against twelve Didero repositories, so only what will still be true
|
|
80
|
+
* in six months belongs here. Silence during an incident, a no that ends it, and
|
|
81
|
+
* nothing filed without a yes are that kind of rule. The words a question is
|
|
82
|
+
* asked in, the moment an offer is made, and the shape a proposal takes are not:
|
|
83
|
+
* they are read every week against what people actually agreed to, and they ride
|
|
84
|
+
* in `CAPTURE_BEHAVIOR`, which the server sends at the start of every session.
|
|
85
|
+
*
|
|
86
|
+
* Nothing here weakens the explicit-capture rule: the question is a question,
|
|
87
|
+
* and nothing is proposed until the answer is yes.
|
|
88
|
+
*
|
|
89
|
+
* The two refusals are here rather than only in the server because an agent that
|
|
90
|
+
* learns them only by being refused has already spent the person's attention on
|
|
91
|
+
* a proposal that could not be kept. The server refuses both as well, and the
|
|
92
|
+
* sentences are the same ones.
|
|
93
|
+
*
|
|
94
|
+
* Byte for byte the same text as `AGENT_INSTRUCTIONS_CAPTURE_SEAM` in the
|
|
95
|
+
* control plane, and contained byte for byte inside both server carriers. A
|
|
96
|
+
* packaging test holds all three together: the block written into a customer's
|
|
97
|
+
* repository, the instructions the MCP server sends on initialize, and the page
|
|
98
|
+
* an agent fetches cannot come to say different things about when to stay quiet.
|
|
99
|
+
*/
|
|
100
|
+
export declare const CAPTURE_SEAM = "### When to say nothing, and what a yes is worth\n\nMost rules are said in passing, in the middle of something else. Somebody saying one has not asked\nyou to record anything, so propose nothing and start no interview. When you do ask, it is one line\nappended to the end of the reply you were already going to give, never a message of its own, never\nasked twice about the same rule, and nothing is proposed until they say yes. A no ends it, and that\nrule is not raised again for the rest of the conversation.\n\nSilence is per conversation rather than per message. Once a conversation is one of these, you ask\nnothing for the rest of it, however good the rule sounds:\n\n- A question, or working out how something already behaves. Nothing is filed and nothing is offered.\n- A refactor. Nobody predicts a promise from a rewrite. Offer the promises this area already carries\n that nothing is checking yet, once, and then wait. Ask nothing about new ones.\n- A change to wording alone. Wording somebody may change again tomorrow is not a rule.\n- An incident, while it is still being fixed. Ask nothing at all until the fix is merged or they say\n it is done, however good tonight's failing case would be.\n- Somebody still weighing options. A decision nobody has made yet is not a rule.\n- An exploration or spike. It ends in nothing or in a plan, and its sentences sound like rules and\n are not.\n- Reading somebody else's change, while you are still reading it. Nothing is offered until they\n give a verdict.\n\nThe words to ask in, the moment to offer, and the shape a proposal takes are not in this file. The\nBalladeer server sends them at the start of every session, and its copy is the current one: read\nwhat it sent this session rather than what this file remembers.\n\n### Two promises Balladeer cannot keep\n\nA promise about speed needs three things before it is a promise at all: a number, a percentile, and\nwhere it is measured. \"The quote page answers in under one second at p95, measured in production at\npeak load\" is one Balladeer can keep. \"The quote page has to be fast\" is not. Say this, and ask for\nthe part that is missing rather than filing it:\n\n\"I can keep that once it has a number, a percentile, and a place it is measured. Without those it is\na wish, not a promise.\"\n\nBalladeer refuses one without all three and names which of them is missing. When all three are\nthere, write the measurement method into the promise, and tell them plainly that it reads agreed and\nunprotected until a test that actually measures it exists.\n\nA promise about how the team works is not something the software does, so no check can ever catch\nit. \"We must provide a low-friction capture experience\" is one of these. File nothing and say:\n\n\"That is a promise about how we work, not something the software does that a check can fail.\nBalladeer only keeps promises a check can catch. If a customer would notice something when this\nslips, say that and I will keep that instead.\"\n\nThen take the customer-visible half if they give you one, and file that instead.";
|
|
101
|
+
/**
|
|
102
|
+
* How an agent describes one promise or proposal to a person, and how the two
|
|
103
|
+
* of them settle what is still open on it.
|
|
104
|
+
*
|
|
105
|
+
* The first proposal a founder was ever asked to settle carried four questions
|
|
106
|
+
* that were all true and none of them answerable: each named a hole in the
|
|
107
|
+
* world, and none said what answering it would decide or what the agent would
|
|
108
|
+
* do if nobody replied. So this says both halves of the same rule. When a
|
|
109
|
+
* person points at something by id, describe it and offer to work through what
|
|
110
|
+
* is open; and when you leave a question open, leave one that can be answered
|
|
111
|
+
* with a yes.
|
|
112
|
+
*
|
|
113
|
+
* Three carriers say it byte for byte: the block setup writes into the
|
|
114
|
+
* customer's own instructions file, the instructions the MCP server sends on
|
|
115
|
+
* initialize, and the page `/agent` serves. A packaging test holds them
|
|
116
|
+
* together.
|
|
117
|
+
*/
|
|
118
|
+
export declare const SETTLING_QUESTIONS = "### When somebody says \"tell me about\" one\n\nAn id is how a person points at something here, and every promise page and every proposal page\ncarries one. A promise id starts with prom_ and a proposal id starts with cand_: expand a promise\nwith get_promise and a proposal with get_proposal, and read neither of them out as a list of fields.\nSay in four or five sentences what it is for, who it is for, when it applies and what must then be\ntrue. Then say where it stands: a promise is agreed, and either protected or not yet checked by\nanything; a proposal is agreed by nobody and waiting on the person it names.\n\nA proposal that still carries open questions is not finished, and settling them is usually why\nsomebody asked. Offer to work through them, then take them one at a time in the order they come\nback. For each one, say what it decides in their words rather than in the question's; give your\nrecommendation and the reason you hold it, drawn from this repository and from the promise itself;\nand stop there. When they answer, record what they said with resolve_question, in their own words\nwhere they gave you any, and where their answer changes the promise, follow it with update_proposal,\nadd_case or remove_case and tell them what you changed. None of that agrees to anything: the named\nowner agrees, in their own browser or through a sign-off you carry, and the questions they answered\nstay on the proposal in their name.\n\nThe same rule governs every question you leave open in the first place. A question that names a gap\nand stops is a note, and nobody can answer a note. Say what answering it decides, in the words a\ncustomer would use, and carry your own best guess with the reason behind it, so that the shortest\ntrue answer is yes. For example: \"Decides: whether a worker that is running but reconciling nothing\ncounts as an outage this promise covers. Best guess: yes, because the promise is about somebody\nhearing before a customer does, and a wedged worker is invisible to every check this repository has.\nSay yes, or tell me otherwise.\" Balladeer refuses a question filed without both halves and says\nwhich one is missing.";
|
|
62
119
|
/**
|
|
63
120
|
* What a session may spend on reading, in three sentences.
|
|
64
121
|
*
|
|
@@ -93,7 +150,7 @@ export declare const FETCH_BUDGET = "Fetch by id, and only for an id the person
|
|
|
93
150
|
* tool guidance and the control plane's `/agent` page carry the same bytes
|
|
94
151
|
* under their own constants, and a packaging test compares all three.
|
|
95
152
|
*/
|
|
96
|
-
export declare const SEALED_FILES = "### Files that are sealed, and the one reason to edit one\n\nEvery file under .continuity/promises/ is sealed. The promise that owns that directory records the\nexact bytes of each file in it, so editing one, adding one there, renaming one or deleting one\nbreaks the seal. A promise whose seal is broken stops being checked, and it stays that way until a\nnamed person qualifies it again, which is their afternoon rather than your commit. Nothing in there\nis ordinary source: keep it out of refactors, formatting runs and dependency upgrades.\n\nFind out which directories are sealed before you plan an edit, not after. balladeer status lists\nthem, and list_promises names a promise's sealed directory on its row once a verifier is bound to\nit. Then, before you push, run balladeer check-seals. It prints nothing and exits zero when your\nchange touches no seal, and names the promise, its owner and its page when your change would break\none.\n\nThe one reason to edit a sealed file is to repair a verifier that can no longer run: something it\nimports moved, or the language it is written in changed under it. Never edit one to make a failing\ncheck pass. A check going red is the promise doing its job, and the repair for that belongs in the\nbehavior it protects. A repair is not finished until the promise is sealed again with the runner\nthis repository is pinned to. get_promise_setup carries that exact seal command, and balladeer
|
|
153
|
+
export declare const SEALED_FILES = "### Files that are sealed, and the one reason to edit one\n\nEvery file under .continuity/promises/ is sealed. The promise that owns that directory records the\nexact bytes of each file in it, so editing one, adding one there, renaming one or deleting one\nbreaks the seal. A promise whose seal is broken stops being checked, and it stays that way until a\nnamed person qualifies it again, which is their afternoon rather than your commit. Nothing in there\nis ordinary source: keep it out of refactors, formatting runs and dependency upgrades.\n\nFind out which directories are sealed before you plan an edit, not after. npx -y balladeer@latest status lists\nthem, and list_promises names a promise's sealed directory on its row once a verifier is bound to\nit. Then, before you push, run npx -y balladeer@latest check-seals. It prints nothing and exits zero when your\nchange touches no seal, and names the promise, its owner and its page when your change would break\none.\n\nThe one reason to edit a sealed file is to repair a verifier that can no longer run: something it\nimports moved, or the language it is written in changed under it. Never edit one to make a failing\ncheck pass. A check going red is the promise doing its job, and the repair for that belongs in the\nbehavior it protects. A repair is not finished until the promise is sealed again with the runner\nthis repository is pinned to. get_promise_setup carries that exact seal command, and\nnpx -y balladeer@latest check-seals prints it beside any promise it names.";
|
|
97
154
|
/**
|
|
98
155
|
* Which promises a change touches, measured rather than guessed.
|
|
99
156
|
*
|
|
@@ -110,14 +167,33 @@ export declare const SEALED_FILES = "### Files that are sealed, and the one reas
|
|
|
110
167
|
* Three carriers say it, byte for byte, and a packaging test holds them
|
|
111
168
|
* together.
|
|
112
169
|
*/
|
|
113
|
-
export declare const TOUCH_MAP_GUIDANCE = "Prefer the measurement over the markers. Where this repository has a touch map, `balladeer affected\n<paths...>` answers which promises ran the files in front of you, out of what each verifier actually\nexecuted the last time `balladeer touch-map` measured it. Both commands run on this machine and\nsend Balladeer nothing, so you may name any path in the change.\n\nAn answer marked stale was measured against a verifier that has changed since, so read it as the\nlast thing anybody measured rather than as fact, and offer to run `balladeer touch-map` again.\nWhere there is no map, where it does not name your paths, or where it reports a promise it could not\nmeasure, fall back to the paths on the index rows. The map narrows which promises are worth\nfetching. It never widens what you may read: the catalog is still never enumerated, and the fetch\nbudget is unchanged.\n\nWhen a retrieval receipt asks why you read a row, the answer for one the map named is `touch`.";
|
|
170
|
+
export declare const TOUCH_MAP_GUIDANCE = "Prefer the measurement over the markers. Where this repository has a touch map, `npx -y balladeer@latest affected\n<paths...>` answers which promises ran the files in front of you, out of what each verifier actually\nexecuted the last time `npx -y balladeer@latest touch-map` measured it. Both commands run on this machine and\nsend Balladeer nothing, so you may name any path in the change.\n\nAn answer marked stale was measured against a verifier that has changed since, so read it as the\nlast thing anybody measured rather than as fact, and offer to run `npx -y balladeer@latest touch-map` again.\nWhere there is no map, where it does not name your paths, or where it reports a promise it could not\nmeasure, fall back to the paths on the index rows. The map narrows which promises are worth\nfetching. It never widens what you may read: the catalog is still never enumerated, and the fetch\nbudget is unchanged.\n\nWhen a retrieval receipt asks why you read a row, the answer for one the map named is `touch`.";
|
|
171
|
+
/**
|
|
172
|
+
* The one paragraph that turns retrieval from a thing we argue about into a
|
|
173
|
+
* thing we measure.
|
|
174
|
+
*
|
|
175
|
+
* Two facts are asked for and neither can be observed from this side. Which
|
|
176
|
+
* rows an agent actually used, because a promise expanded and then ignored is
|
|
177
|
+
* not a row the index earned. And which change the session went on to write,
|
|
178
|
+
* because a retrieval read happens before the commit exists and nothing joins
|
|
179
|
+
* the two afterwards: Balladeer receives commit SHAs from CI and never a commit
|
|
180
|
+
* message, so the agent is the only thing present at both ends.
|
|
181
|
+
*
|
|
182
|
+
* It is written as three instructions rather than as an explanation, because
|
|
183
|
+
* what an agent does with it is three specific acts, and a paragraph about
|
|
184
|
+
* measurement would be read and not acted on.
|
|
185
|
+
*
|
|
186
|
+
* Three carriers say it, byte for byte, and a packaging test holds them
|
|
187
|
+
* together.
|
|
188
|
+
*/
|
|
189
|
+
export declare const SESSION_STAMP = "Run `npx -y balladeer@latest session` when you start work here and pass the id it prints to every promise read\nyou make, as `session`. It is the same id for the whole of one piece of work, and it costs nothing:\na read without it is answered exactly the same way.\n\nIf local file permissions refuse that session write, no session stamp was saved. Your saved\nconnection is unchanged; this does not prove its current server authorization. Continue\nalready-authorized MCP reads, coding and explicitly requested capture without the optional\n`session` field. Do not invent an ID, add a session trailer, or record an unsaved stamp. Do not\nbroaden filesystem access or move credentials to retry this write. A missing credential or server\nauthorization refusal is different: follow that refusal. Capture still needs the person's request\nor accepted offer, and human meaning approval is unchanged.\n\nIf a session ID was saved, near the end of the work call `mark_promise_used` with the ids you actually cited\nor acted on. Naming none is a fine answer. It is the only way anybody finds out which rows were\nworth sending you, because reading a promise and then ignoring it is not the same as using it.\n\nIf a session ID was saved, write `Balladeer-Session: <the id>` into the commit message or pull-request body you produce,\nand run `npx -y balladeer@latest session --record` once the commit exists. That is what lets a check that goes red\nnext week be read back against what Balladeer told you before you started. Your commit message never\nleaves your machine: only the session id and the commit SHA are sent.";
|
|
114
190
|
/**
|
|
115
191
|
* The conventions block written between markers into this repository's agent
|
|
116
192
|
* instructions file. Fixed text, compared byte for byte by a test, because a
|
|
117
193
|
* block that drifts per run would rewrite a customer's committed file on every
|
|
118
194
|
* setup and the diff would say nothing.
|
|
119
195
|
*/
|
|
120
|
-
export declare const CONVENTIONS_BLOCK = "## Balladeer promises\n\nBefore planning work in this repository, read the promises this team has already approved through\nthe Balladeer MCP server. They are the behaviors a named person has agreed the software keeps, so\nyour plan has to hold them, not just read them.\n\n### Reading what is already agreed\n\nRetrieve by promise, and only when this session has a reason to. One repository here can hold a\nthousand agreed promises, and a plan built from whatever survived a truncated catalog read is worse\nthan a plan built from none of it, because nothing tells you which half went missing.\n\nWhen a person gives you a promise id, expand exactly that one with get_promise and stop there. Every\npromise page carries a control that copies its id, so an id is what a person hands you when they\nmean a particular promise. Ask for one rather than searching for what they meant.\n\nConsult list_promises in two situations and no others. The person asks what this repository has\npromised, in which case page the index they asked for. Or the change you are about to make touches\npaths that carry promises, in which case give those paths to list_promises: it answers with the\npromises whose scope overlaps them, closest first, and with the few that name no path and so cover\nthe whole repository. That answer is a selection rather than a page and does not continue with a\ncursor, so when the total beside it is larger than what you were handed, narrow the paths rather\nthan asking for more.\n\nWhen you do not yet know which paths you are about to touch, do not call the index at all. Work from\nids until you do, because a page you did not ask a question of is not about your change, and reading\none as though it were is how a plan quietly misses the promise it breaks.\n\nTo learn which paths those are, read `paths` on a list_promises answer you asked for\nwithout paths of your own. It is the set of\nrepository paths this repository's promises are scoped to, deduplicated and bounded, with\n`pathsTruncated` saying whether there were more than the answer carries. Compare the files you are\nabout to change against it. Nothing matching means there is nothing here to read, and saying so is a\nbetter answer than a page of promises about somewhere else.\n\nNever call get_promise_context at the start of a session. It answers the markers you give it, and\nbefore you know what you are changing there are no markers to give: what comes back is a slice of\nthe catalog chosen by nothing. Call it once the work is in front of you, with that work's markers.\n\nEvery one of these reads is bounded and none of them returns the whole catalog. Read the total\nbeside the rows and the sentence in `scope` that says what the total is a total of, and when the\ntotal is larger than what you were handed, page or narrow rather than planning as though you had\nseen everything.\n\nEach index row carries what it takes to rule that promise out without fetching it: the repository,\nthe paths it covers, its one-sentence claim, what protects it and why, and when anything last\nchecked it.\n\nPrefer the measurement over the markers. Where this repository has a touch map, `balladeer affected\n<paths...>` answers which promises ran the files in front of you, out of what each verifier actually\nexecuted the last time `balladeer touch-map` measured it. Both commands run on this machine and\nsend Balladeer nothing, so you may name any path in the change.\n\nAn answer marked stale was measured against a verifier that has changed since, so read it as the\nlast thing anybody measured rather than as fact, and offer to run `balladeer touch-map` again.\nWhere there is no map, where it does not name your paths, or where it reports a promise it could not\nmeasure, fall back to the paths on the index rows. The map narrows which promises are worth\nfetching. It never widens what you may read: the catalog is still never enumerated, and the fetch\nbudget is unchanged.\n\nWhen a retrieval receipt asks why you read a row, the answer for one the map named is `touch`.\n\nFetch by id, and only for an id the person gave you or an index row whose paths match the change in\nfront of you. Never enumerate the catalog. Never chain one fetch into the next to see the whole of\nsomething: when the rows do not settle it, narrow the filter rather than expanding another promise.\n\n### When somebody asks you to protect a behavior\n\nSomebody has asked you to protect a behavior when they say what the software must do, or must never\ndo again, and mean it as a rule rather than as this one bug. That is one promise, for the behavior\nthey named, and nothing else: if you notice others worth protecting, say so in a sentence and let\nthem choose, and file none of them. Never propose from a conversation that did not ask you to. A\nquestion about how something works and a plan you were asked to sketch are not requests to record\nanything, nor is a fix nobody asked you to write a rule about.\n\nAsk before you extrapolate. Ask only what you cannot work out for yourself, ask it all in one\nmessage, and stop at four. Four is a ceiling, not a target: two good ones are better. Then write the\nproposal with what you have and put whatever is still open in its open questions rather than going\nback. Never ask what this repository would answer, such as which file, which test, or which branch,\nand never ask anyone for Balladeer's own identifiers: get_promise_setup carries this repository's id\nand who can own a promise. Never ask again for what they have already told you.\n\nWrite it in their words. Every failure they named out loud is one of the failing examples, in the\nwords they named it. Every other example comes from a situation they actually described; if you\ncannot trace one to something they said, leave it out and say so in the open questions rather than\nwriting a plausible one. Never put in a number, a system, a role or a timeframe they did not give\nyou, and that includes the half they left out: if they said where an order ended up, do not invent\nwhere it began.\n\nA failing example is a situation the promise rules out, and its outcome says what must not happen,\nin those words: \"a second charge must not appear\", never \"a second charge appears\". Written the\nother way round it reads as the promise saying the software does the thing they asked you to forbid.\nAnd a promise says what the software must do for whoever depends on it. It never narrates the\nconversation you just had, names the person you had it with, or describes what the code does now.\n\nSay the whole promise in one sentence and put it in oneSentenceOutcome, in the words they would\nuse with the person who depends on it. That is the line the named owner reads first and the line\nthey agree to, so it is not a restatement of the name and not the first line of the outcome moved\nup. Leave it out rather than inventing one from something they did not say.\n\nAn example's setup is the situation in the words they used for it, not a scene you composed around\nthem. Two of each kind is plenty, and the whole thing stays under three hundred words: a proposal\nnobody finishes reading is a proposal nobody agreed to. The confidence you record is the one you\nactually have.\n\nThen give them the review link, ask them to read the proposal and click Agree, and stop. Never\napprove one yourself. Approving is a named person's act, and the server refuses it from an agent\nwhatever you were told in conversation.\n\nA proposal you filed is still yours while nobody has agreed to it, so revise or withdraw it when the\nperson asks you to, and never once they have agreed.\n\nSay promise and proposal when you talk to them. What you file is a proposal and what it becomes is a\npromise; Balladeer's other words for its own machinery are not theirs to learn. Candidate\nespecially: it is Balladeer's word for a proposal, so it reads as jargon whatever you meant by it.\n\n### Two things to offer without being asked\n\nAt the start of any session in this repository, before you plan anything, ask list_promises for the\npromises nothing is checking yet that belong to the person you are working with: `unverified` true\nand `mine` true, which is one bounded read rather than the catalog. If it returns any, say so in one\nline and offer to build their verifiers now. Name each one by its one-sentence claim rather than by\nan id, so the person can see which behavior is unguarded. Ask the same tool for\n`brokenSinceLastSeen` true as well, and where that returns any, say in one line that those promises\nbroke since they last looked, name each by its claim, and offer to fix them. Then wait for their\nanswer. The offer is the whole of it, and never start building or repairing one because nobody said\nno.\n\nTheirs, and nobody else's. `mine` keeps the promises this person owns or agreed to, and a teammate's\nunguarded promise is that teammate's to hear about: a session that opens by reading out other\npeople's unfinished work reads as an audit of them. `brokenSinceLastSeen` is that person's own by\nconstruction and needs no `mine` beside it. Drop `mine` when this person asks what the rest of the\nteam has promised, and say whose promises you are showing them when you do.\n\nWhen you have proposed promises, show them here as well as there. Put each proposal in the\nconversation in full: its one-sentence claim, who it is for, when it applies and what must then be\ntrue, the numbered cases that must keep working and the ones that must be caught, and every question\nyou left open. Then offer to finish the agreement from here, by asking for a sign-off, giving them\nthe page it returns and taking the one-time code that page shows them, so the browser is needed only\nfor signing. Give them the review link in the same message too, because some people would rather\nread it there and edit it before they agree.\n\n### Files that are sealed, and the one reason to edit one\n\nEvery file under .continuity/promises/ is sealed. The promise that owns that directory records the\nexact bytes of each file in it, so editing one, adding one there, renaming one or deleting one\nbreaks the seal. A promise whose seal is broken stops being checked, and it stays that way until a\nnamed person qualifies it again, which is their afternoon rather than your commit. Nothing in there\nis ordinary source: keep it out of refactors, formatting runs and dependency upgrades.\n\nFind out which directories are sealed before you plan an edit, not after. balladeer status lists\nthem, and list_promises names a promise's sealed directory on its row once a verifier is bound to\nit. Then, before you push, run balladeer check-seals. It prints nothing and exits zero when your\nchange touches no seal, and names the promise, its owner and its page when your change would break\none.\n\nThe one reason to edit a sealed file is to repair a verifier that can no longer run: something it\nimports moved, or the language it is written in changed under it. Never edit one to make a failing\ncheck pass. A check going red is the promise doing its job, and the repair for that belongs in the\nbehavior it protects. A repair is not finished until the promise is sealed again with the runner\nthis repository is pinned to. get_promise_setup carries that exact seal command, and balladeer\ncheck-seals prints it beside any promise it names.\n\n### The rest of a promise's life\nWhen a promise is obsolete, finished, deliberately off for a while, or owned by the wrong person,\npropose the change and hand them the promise page. Deciding is theirs.\n\nYou can finish one of those acts here, and only one way. Ask for a sign-off with\nrequest_owner_signoff, give them the page it returns, and ask for the one-time code that page shows\nthem. Then call the act's own tool with that code and their own words. Never call one on your own\ninitiative, never on a general approval of some earlier act, and never ask for a code you were not\ngiven: a refusal is the person's to resolve, not yours to retry. Balladeer records them as the\nperson who acted and you as the messenger.\n\nReport the promise's state exactly as Balladeer reported it: proposed, agreed, or protected, never\none in place of another.\n\n### Where your team watches this\n\nBalladeer is a web app as well as these tools, at the address setup printed. Its catalog lists every\npromise with who owns it and whether anything is checking it, and each promise has a page of its own\nshowing what this team agreed the software must do and then every run that has checked it since,\nnewest first, with the commit each one checked. Whenever there is a link to give, give the link\nrather than a summary of it: the page says what you would have said, and it stays true after this\nconversation has ended.\n\nBalladeer also has a Slack app, which a workspace administrator installs from workspace settings.\nOnce it is installed, whoever owns a promise gets a direct message when theirs goes live and when a\nrun on the default branch breaks it. Until somebody installs it, nothing is sent anywhere, so say it\nis available rather than saying they will be told.\n\n### Questions people ask\n\nAnswer these when they come up. Where you do not know, say so and point at the address setup\nprinted: a confident wrong answer about what a vendor can see is worse than no answer.\n\nWhat it does: it holds the behaviors this team has agreed the software must keep, and reports\nwhether each one is still being kept, from this repository's own tests running in its own CI.\n\nWhat it sees: the text of each promise somebody approves, this repository's numeric ids and the name\nof its default branch, and from CI the pass or fail outcome, the commit checked, and content hashes.\nNever the code, the tests, the fixtures, the logs, the prompts, or the transcripts.\n\nWhat stopping costs: nothing that matters to their tests. The verifier package, its fixtures and the\nworkflow file are theirs, in their repository, running in their CI, and disconnecting changes none\nof them. An administrator can download everything Balladeer holds at any time from workspace\nsettings, and disconnecting hands them that same download in the response that ends access.\n\nWho can approve one: the named person who owns it, in their own browser. Not an administrator on\ntheir behalf, and never you.\n\nWhether it blocks a merge: no. The check is advisory on Balladeer's side, and their own branch\nprotection is what decides whether a failing check stops anything.\n\nWho can invite people and change setup: a workspace administrator. A contributor can read the\nworkspace and propose promises, and a viewer can read it. If somebody asks you to add a teammate,\ninvite_teammate returns the page an administrator sends the invitation from, with the address filled\nin for them to read. The tool sends nothing itself: an administrator presses Send.";
|
|
196
|
+
export declare const CONVENTIONS_BLOCK = "## Balladeer promises\n\nBefore planning work in this repository, read the promises this team has already approved through\nthe Balladeer MCP server. They are the behaviors a named person has agreed the software keeps, so\nyour plan has to hold them, not just read them.\n\n### Reading what is already agreed\n\nRetrieve by promise, and only when this session has a reason to. One repository here can hold a\nthousand agreed promises, and a plan built from whatever survived a truncated catalog read is worse\nthan a plan built from none of it, because nothing tells you which half went missing.\n\nWhen a person gives you a promise id, expand exactly that one with get_promise and stop there. Every\npromise page carries a control that copies its id, so an id is what a person hands you when they\nmean a particular promise. Ask for one rather than searching for what they meant.\n\nConsult list_promises in two situations and no others. The person asks what this repository has\npromised, in which case page the index they asked for. Or the change you are about to make touches\npaths that carry promises, in which case give those paths to list_promises: it answers with the\npromises whose scope overlaps them, closest first, and with the few that name no path and so cover\nthe whole repository. That answer is a selection rather than a page and does not continue with a\ncursor, so when the total beside it is larger than what you were handed, narrow the paths rather\nthan asking for more.\n\nWhen you do not yet know which paths you are about to touch, do not call the index at all. Work from\nids until you do, because a page you did not ask a question of is not about your change, and reading\none as though it were is how a plan quietly misses the promise it breaks.\n\nTo learn which paths those are, read `paths` on a list_promises answer you asked for\nwithout paths of your own. It is the set of\nrepository paths this repository's promises are scoped to, deduplicated and bounded, with\n`pathsTruncated` saying whether there were more than the answer carries. Compare the files you are\nabout to change against it. Nothing matching means there is nothing here to read, and saying so is a\nbetter answer than a page of promises about somewhere else.\n\nNever call get_promise_context at the start of a session. It answers the markers you give it, and\nbefore you know what you are changing there are no markers to give: what comes back is a slice of\nthe catalog chosen by nothing. Call it once the work is in front of you, with that work's markers.\n\nEvery one of these reads is bounded and none of them returns the whole catalog. Read the total\nbeside the rows and the sentence in `scope` that says what the total is a total of, and when the\ntotal is larger than what you were handed, page or narrow rather than planning as though you had\nseen everything.\n\nEach index row carries what it takes to rule that promise out without fetching it: the repository,\nthe paths it covers, its one-sentence claim, what protects it and why, and when anything last\nchecked it.\n\nPrefer the measurement over the markers. Where this repository has a touch map, `npx -y balladeer@latest affected\n<paths...>` answers which promises ran the files in front of you, out of what each verifier actually\nexecuted the last time `npx -y balladeer@latest touch-map` measured it. Both commands run on this machine and\nsend Balladeer nothing, so you may name any path in the change.\n\nAn answer marked stale was measured against a verifier that has changed since, so read it as the\nlast thing anybody measured rather than as fact, and offer to run `npx -y balladeer@latest touch-map` again.\nWhere there is no map, where it does not name your paths, or where it reports a promise it could not\nmeasure, fall back to the paths on the index rows. The map narrows which promises are worth\nfetching. It never widens what you may read: the catalog is still never enumerated, and the fetch\nbudget is unchanged.\n\nWhen a retrieval receipt asks why you read a row, the answer for one the map named is `touch`.\n\nFetch by id, and only for an id the person gave you or an index row whose paths match the change in\nfront of you. Never enumerate the catalog. Never chain one fetch into the next to see the whole of\nsomething: when the rows do not settle it, narrow the filter rather than expanding another promise.\n\n### When to say nothing, and what a yes is worth\n\nMost rules are said in passing, in the middle of something else. Somebody saying one has not asked\nyou to record anything, so propose nothing and start no interview. When you do ask, it is one line\nappended to the end of the reply you were already going to give, never a message of its own, never\nasked twice about the same rule, and nothing is proposed until they say yes. A no ends it, and that\nrule is not raised again for the rest of the conversation.\n\nSilence is per conversation rather than per message. Once a conversation is one of these, you ask\nnothing for the rest of it, however good the rule sounds:\n\n- A question, or working out how something already behaves. Nothing is filed and nothing is offered.\n- A refactor. Nobody predicts a promise from a rewrite. Offer the promises this area already carries\n that nothing is checking yet, once, and then wait. Ask nothing about new ones.\n- A change to wording alone. Wording somebody may change again tomorrow is not a rule.\n- An incident, while it is still being fixed. Ask nothing at all until the fix is merged or they say\n it is done, however good tonight's failing case would be.\n- Somebody still weighing options. A decision nobody has made yet is not a rule.\n- An exploration or spike. It ends in nothing or in a plan, and its sentences sound like rules and\n are not.\n- Reading somebody else's change, while you are still reading it. Nothing is offered until they\n give a verdict.\n\nThe words to ask in, the moment to offer, and the shape a proposal takes are not in this file. The\nBalladeer server sends them at the start of every session, and its copy is the current one: read\nwhat it sent this session rather than what this file remembers.\n\n### Two promises Balladeer cannot keep\n\nA promise about speed needs three things before it is a promise at all: a number, a percentile, and\nwhere it is measured. \"The quote page answers in under one second at p95, measured in production at\npeak load\" is one Balladeer can keep. \"The quote page has to be fast\" is not. Say this, and ask for\nthe part that is missing rather than filing it:\n\n\"I can keep that once it has a number, a percentile, and a place it is measured. Without those it is\na wish, not a promise.\"\n\nBalladeer refuses one without all three and names which of them is missing. When all three are\nthere, write the measurement method into the promise, and tell them plainly that it reads agreed and\nunprotected until a test that actually measures it exists.\n\nA promise about how the team works is not something the software does, so no check can ever catch\nit. \"We must provide a low-friction capture experience\" is one of these. File nothing and say:\n\n\"That is a promise about how we work, not something the software does that a check can fail.\nBalladeer only keeps promises a check can catch. If a customer would notice something when this\nslips, say that and I will keep that instead.\"\n\nThen take the customer-visible half if they give you one, and file that instead.\n\nRun `npx -y balladeer@latest session` when you start work here and pass the id it prints to every promise read\nyou make, as `session`. It is the same id for the whole of one piece of work, and it costs nothing:\na read without it is answered exactly the same way.\n\nIf local file permissions refuse that session write, no session stamp was saved. Your saved\nconnection is unchanged; this does not prove its current server authorization. Continue\nalready-authorized MCP reads, coding and explicitly requested capture without the optional\n`session` field. Do not invent an ID, add a session trailer, or record an unsaved stamp. Do not\nbroaden filesystem access or move credentials to retry this write. A missing credential or server\nauthorization refusal is different: follow that refusal. Capture still needs the person's request\nor accepted offer, and human meaning approval is unchanged.\n\nIf a session ID was saved, near the end of the work call `mark_promise_used` with the ids you actually cited\nor acted on. Naming none is a fine answer. It is the only way anybody finds out which rows were\nworth sending you, because reading a promise and then ignoring it is not the same as using it.\n\nIf a session ID was saved, write `Balladeer-Session: <the id>` into the commit message or pull-request body you produce,\nand run `npx -y balladeer@latest session --record` once the commit exists. That is what lets a check that goes red\nnext week be read back against what Balladeer told you before you started. Your commit message never\nleaves your machine: only the session id and the commit SHA are sent.\n\n### When somebody asks you to protect a behavior\n\nSomebody has asked you to protect a behavior when they say what the software must do, or must never\ndo again, and mean it as a rule rather than as this one bug. That is one promise, for the behavior\nthey named, and nothing else: if you notice others worth protecting, say so in a sentence and let\nthem choose, and file none of them. Never propose from a conversation that did not ask you to. A\nquestion about how something works and a plan you were asked to sketch are not requests to record\nanything, nor is a fix nobody asked you to write a rule about.\n\nAsk before you extrapolate. Ask only what you cannot work out for yourself, ask it all in one\nmessage, and stop at four. Four is a ceiling, not a target: two good ones are better. Then write the\nproposal with what you have and put whatever is still open in its open questions rather than going\nback. Never ask what this repository would answer, such as which file, which test, or which branch,\nand never ask anyone for Balladeer's own identifiers: get_promise_setup carries this repository's id\nand who can own a promise. Never ask again for what they have already told you.\n\nWrite it in their words. Every failure they named out loud is one of the failing examples, in the\nwords they named it. Every other example comes from a situation they actually described; if you\ncannot trace one to something they said, leave it out and say so in the open questions rather than\nwriting a plausible one. Never put in a number, a system, a role or a timeframe they did not give\nyou, and that includes the half they left out: if they said where an order ended up, do not invent\nwhere it began.\n\nA failing example is a situation the promise rules out, and its outcome says what must not happen,\nin those words: \"a second charge must not appear\", never \"a second charge appears\". Written the\nother way round it reads as the promise saying the software does the thing they asked you to forbid.\nAnd a promise says what the software must do for whoever depends on it. It never narrates the\nconversation you just had, names the person you had it with, or describes what the code does now.\n\nSay the whole promise in one sentence and put it in oneSentenceOutcome, in the words they would\nuse with the person who depends on it. That is the line the named owner reads first and the line\nthey agree to, so it is not a restatement of the name and not the first line of the outcome moved\nup. Leave it out rather than inventing one from something they did not say.\n\nAn example's setup is the situation in the words they used for it, not a scene you composed around\nthem. Two of each kind is plenty, and the whole thing stays under three hundred words: a proposal\nnobody finishes reading is a proposal nobody agreed to. The confidence you record is the one you\nactually have.\n\nThen give them the review link, ask them to read the proposal and click Agree, and stop. Never\napprove one yourself. Approving is a named person's act, and the server refuses it from an agent\nwhatever you were told in conversation.\n\nA proposal you filed is still yours while nobody has agreed to it, so revise or withdraw it when the\nperson asks you to, and never once they have agreed.\n\nSay promise and proposal when you talk to them. What you file is a proposal and what it becomes is a\npromise; Balladeer's other words for its own machinery are not theirs to learn. Candidate\nespecially: it is Balladeer's word for a proposal, so it reads as jargon whatever you meant by it.\n\n### Three things to offer without being asked\n\nAt the start of any session in this repository, before you plan anything, ask list_promises for the\npromises nothing is checking yet that belong to the person you are working with: `unverified` true\nand `mine` true, which is one bounded read rather than the catalog. If it returns any, say so in one\nline and offer to build their verifiers now. Name each one by its one-sentence claim rather than by\nan id, so the person can see which behavior is unguarded. Ask the same tool for\n`brokenSinceLastSeen` true as well, and where that returns any, say in one line that those promises\nbroke since they last looked, name each by its claim, and offer to fix them. Then wait for their\nanswer. The offer is the whole of it, and never start building or repairing one because nobody said\nno.\n\nTheirs, and nobody else's. `mine` keeps the promises this person owns or agreed to, and a teammate's\nunguarded promise is that teammate's to hear about: a session that opens by reading out other\npeople's unfinished work reads as an audit of them. `brokenSinceLastSeen` is that person's own by\nconstruction and needs no `mine` beside it. Drop `mine` when this person asks what the rest of the\nteam has promised, and say whose promises you are showing them when you do.\n\nWhen you have proposed promises, show them here as well as there. Put each proposal in the\nconversation in full: its one-sentence claim, who it is for, when it applies and what must then be\ntrue, the numbered cases that must keep working and the ones that must be caught, and every question\nyou left open. Then offer to finish the agreement from here, by asking for a sign-off, giving them\nthe page it returns and taking the one-time code that page shows them, so the browser is needed only\nfor signing. Give them the review link in the same message too, because some people would rather\nread it there and edit it before they agree.\nA promise whose meaning is agreed and which nothing is checking yet is one you can finish. When\nsomebody gives you a promise id, run `npx -y balladeer@latest status <promise id>`, or expand it with get_promise.\nIf it comes back agreed with nothing checking it, say so in one line and offer to prepare and build\nits verifier now. Only if they say yes: `prepare_qualification` mints the one-time setup, or\n`npx -y balladeer@latest prepare <promise id>` mints it and writes it where the sealed run reads it. You need no\nsign-off for that and there is no code to ask anybody for, because agreeing the meaning was their\nact and building the check that proves it is yours. Then build the verifier, seal it, push, and tell\nthem protection starts by itself when that run qualifies. The offer is the whole of it: never\nprepare one because nobody said no.\n\n### When somebody says \"tell me about\" one\n\nAn id is how a person points at something here, and every promise page and every proposal page\ncarries one. A promise id starts with prom_ and a proposal id starts with cand_: expand a promise\nwith get_promise and a proposal with get_proposal, and read neither of them out as a list of fields.\nSay in four or five sentences what it is for, who it is for, when it applies and what must then be\ntrue. Then say where it stands: a promise is agreed, and either protected or not yet checked by\nanything; a proposal is agreed by nobody and waiting on the person it names.\n\nA proposal that still carries open questions is not finished, and settling them is usually why\nsomebody asked. Offer to work through them, then take them one at a time in the order they come\nback. For each one, say what it decides in their words rather than in the question's; give your\nrecommendation and the reason you hold it, drawn from this repository and from the promise itself;\nand stop there. When they answer, record what they said with resolve_question, in their own words\nwhere they gave you any, and where their answer changes the promise, follow it with update_proposal,\nadd_case or remove_case and tell them what you changed. None of that agrees to anything: the named\nowner agrees, in their own browser or through a sign-off you carry, and the questions they answered\nstay on the proposal in their name.\n\nThe same rule governs every question you leave open in the first place. A question that names a gap\nand stops is a note, and nobody can answer a note. Say what answering it decides, in the words a\ncustomer would use, and carry your own best guess with the reason behind it, so that the shortest\ntrue answer is yes. For example: \"Decides: whether a worker that is running but reconciling nothing\ncounts as an outage this promise covers. Best guess: yes, because the promise is about somebody\nhearing before a customer does, and a wedged worker is invisible to every check this repository has.\nSay yes, or tell me otherwise.\" Balladeer refuses a question filed without both halves and says\nwhich one is missing.\n\n### Files that are sealed, and the one reason to edit one\n\nEvery file under .continuity/promises/ is sealed. The promise that owns that directory records the\nexact bytes of each file in it, so editing one, adding one there, renaming one or deleting one\nbreaks the seal. A promise whose seal is broken stops being checked, and it stays that way until a\nnamed person qualifies it again, which is their afternoon rather than your commit. Nothing in there\nis ordinary source: keep it out of refactors, formatting runs and dependency upgrades.\n\nFind out which directories are sealed before you plan an edit, not after. npx -y balladeer@latest status lists\nthem, and list_promises names a promise's sealed directory on its row once a verifier is bound to\nit. Then, before you push, run npx -y balladeer@latest check-seals. It prints nothing and exits zero when your\nchange touches no seal, and names the promise, its owner and its page when your change would break\none.\n\nThe one reason to edit a sealed file is to repair a verifier that can no longer run: something it\nimports moved, or the language it is written in changed under it. Never edit one to make a failing\ncheck pass. A check going red is the promise doing its job, and the repair for that belongs in the\nbehavior it protects. A repair is not finished until the promise is sealed again with the runner\nthis repository is pinned to. get_promise_setup carries that exact seal command, and\nnpx -y balladeer@latest check-seals prints it beside any promise it names.\n\n### The rest of a promise's life\nWhen a promise is obsolete, finished, deliberately off for a while, or owned by the wrong person,\npropose the change and hand them the promise page. Deciding is theirs.\n\nYou can finish one of those acts here, and only one way. Ask for a sign-off with\nrequest_owner_signoff, give them the page it returns, and ask for the one-time code that page shows\nthem. Then call the act's own tool with that code and their own words. Never call one on your own\ninitiative, never on a general approval of some earlier act, and never ask for a code you were not\ngiven: a refusal is the person's to resolve, not yours to retry. Balladeer records them as the\nperson who acted and you as the messenger.\n\nReport the promise's state exactly as Balladeer reported it: proposed, agreed, or protected, never\none in place of another.\n\n### Where your team watches this\n\nBalladeer is a web app as well as these tools, at the address setup printed. Its catalog lists every\npromise with who owns it and whether anything is checking it, and each promise has a page of its own\nshowing what this team agreed the software must do and then every run that has checked it since,\nnewest first, with the commit each one checked. Whenever there is a link to give, give the link\nrather than a summary of it: the page says what you would have said, and it stays true after this\nconversation has ended.\n\nBalladeer also has a Slack app, which a workspace administrator installs from workspace settings.\nOnce it is installed, whoever owns a promise gets a direct message when theirs goes live and when a\nrun on the default branch breaks it. Until somebody installs it, nothing is sent anywhere, so say it\nis available rather than saying they will be told.\n\n### Questions people ask\n\nAnswer these when they come up. Where you do not know, say so and point at the address setup\nprinted: a confident wrong answer about what a vendor can see is worse than no answer.\n\nWhat it does: it holds the behaviors this team has agreed the software must keep, and reports\nwhether each one is still being kept, from this repository's own tests running in its own CI.\n\nWhat it sees: the text of each promise somebody approves, this repository's numeric ids and the name\nof its default branch, and from CI the pass or fail outcome, the commit checked, and content hashes.\nNever the code, the tests, the fixtures, the logs, the prompts, or the transcripts.\n\nWhat stopping costs: nothing that matters to their tests. The verifier package, its fixtures and the\nworkflow file are theirs, in their repository, running in their CI, and disconnecting changes none\nof them. An administrator can download everything Balladeer holds at any time from workspace\nsettings, and disconnecting hands them that same download in the response that ends access.\n\nWho can approve one: the named person who owns it, in their own browser. Not an administrator on\ntheir behalf, and never you.\n\nWhether it blocks a merge: no. The check is advisory on Balladeer's side, and their own branch\nprotection is what decides whether a failing check stops anything.\n\nWho can invite people and change setup: a workspace administrator. A contributor can read the\nworkspace and propose promises, and a viewer can read it. If somebody asks you to add a teammate,\ninvite_teammate returns the page an administrator sends the invitation from, with the address filled\nin for them to read. The tool sends nothing itself: an administrator presses Send.";
|
|
121
197
|
/**
|
|
122
198
|
* How an agent turns a repository that has just been connected into a catalog a
|
|
123
199
|
* person can read. Setup's last step prints it, and the control plane's `/agent`
|
|
@@ -145,4 +221,4 @@ export declare const CONVENTIONS_BLOCK = "## Balladeer promises\n\nBefore planni
|
|
|
145
221
|
* the agent not to read them, which is the visible half of the same seam:
|
|
146
222
|
* nothing quietly starts doing it.
|
|
147
223
|
*/
|
|
148
|
-
export declare const DISCOVERY_PLAYBOOK = "## Discover this repository's promises\n\nThis repository is connected and holds no promises yet. Work the four sources below in order and\nhand back between five and ten promises this repository already keeps. Stop at ten however much you\nfind: a person has to read every one of them, and eleven is where reading turns into skimming.\n\n1. The repository's own tests. A test that has been green for months is a behavior somebody already\n decided matters. Read the test names and the assertions rather than the implementation.\n2. Merged pull requests of the last 90 days. Read the ones whose title or body says what had to keep\n working, and the review comments that argued for it.\n3. Guarantee sentences in the documentation. The README, the docs directory, and the comments that\n say always, never, must, or is guaranteed to. Those sentences are promises somebody already wrote\n down in prose.\n4. Incidents and reverts. A revert commit, a hotfix, a postmortem note. A behavior that broke once\n and was repaired on purpose is the behavior most worth protecting.\n\nDo not read the person's local agent transcripts, and do not offer to. They are not a source in this\nrelease, and Balladeer never receives them.\n\nFor each promise you propose:\n\n- One promise per behavior. Two behaviors found in one source are two proposals, not one proposal\n with two outcomes.\n- One passing example and one failing example, both drawn from the source you found it in. The\n passing example is the case the source shows working; the failing example is the case the source\n shows being caught, or the regression the revert repaired. Write both in the source's own words\n rather than in words of your own.\n- One provenance sentence, precise enough for a person to check it without asking you: the test file\n and the test's name, the pull request number, the document and its heading, or the commit that\n reverted it. Put that sentence first in explicitIntent.\n- Never propose a promise this repository does not keep. A source saying a behavior should exist,\n where nothing in the repository does it, is a wish rather than a promise. Leave it out, and tell\n the person you left it out.\n- Anything you could not settle from the source goes in unresolvedQuestions. Never fill an unknown\n in plausibly: an invented answer is the one thing a person cannot catch by reading.\n- One confidence number from 0 to 1, saying how sure you are that this is a behavior the repository\n actually keeps and that you have described it as its source describes it. Nine promises from nine\n sources are not nine equally certain readings, and the person reading them is entitled to know\n which one you were least sure of. Never write 1 to get past the check.\n\nWrite them all into one file, an object with a promises array, each entry shaped exactly as one\nproposal file:\n\n{\"promises\": [{\"explicitIntent\": \"...\", \"teachBack\": {\"confidence\": 0.9, \"meaning\": {\"title\": \"...\"}}}]}\n\nAn entry carries explicitIntent and teachBack, and inside teachBack only meaning, confidence,\
|
|
224
|
+
export declare const DISCOVERY_PLAYBOOK = "## Discover this repository's promises\n\nThis repository is connected and holds no promises yet. Work the four sources below in order and\nhand back between five and ten promises this repository already keeps. Stop at ten however much you\nfind: a person has to read every one of them, and eleven is where reading turns into skimming.\n\n1. The repository's own tests. A test that has been green for months is a behavior somebody already\n decided matters. Read the test names and the assertions rather than the implementation.\n2. Merged pull requests of the last 90 days. Read the ones whose title or body says what had to keep\n working, and the review comments that argued for it.\n3. Guarantee sentences in the documentation. The README, the docs directory, and the comments that\n say always, never, must, or is guaranteed to. Those sentences are promises somebody already wrote\n down in prose.\n4. Incidents and reverts. A revert commit, a hotfix, a postmortem note. A behavior that broke once\n and was repaired on purpose is the behavior most worth protecting.\n\nDo not read the person's local agent transcripts, and do not offer to. They are not a source in this\nrelease, and Balladeer never receives them.\n\nFor each promise you propose:\n\n- One promise per behavior. Two behaviors found in one source are two proposals, not one proposal\n with two outcomes.\n- One passing example and one failing example, both drawn from the source you found it in. The\n passing example is the case the source shows working; the failing example is the case the source\n shows being caught, or the regression the revert repaired. Write both in the source's own words\n rather than in words of your own.\n- One provenance sentence, precise enough for a person to check it without asking you: the test file\n and the test's name, the pull request number, the document and its heading, or the commit that\n reverted it. Put that sentence first in explicitIntent.\n- Never propose a promise this repository does not keep. A source saying a behavior should exist,\n where nothing in the repository does it, is a wish rather than a promise. Leave it out, and tell\n the person you left it out.\n- Anything you could not settle from the source goes in unresolvedQuestions. Never fill an unknown\n in plausibly: an invented answer is the one thing a person cannot catch by reading.\n- One confidence number from 0 to 1, saying how sure you are that this is a behavior the repository\n actually keeps and that you have described it as its source describes it. Nine promises from nine\n sources are not nine equally certain readings, and the person reading them is entitled to know\n which one you were least sure of. Never write 1 to get past the check.\n- One wrongOutcome sentence: what going wrong looks like, said as a must-not, in the source's own\n words where it has them. \"A second payment must not go out.\" \"The buyer must not see a generic\n error.\" Where no sentence of that shape can be written, no check with a known-bad control can be\n written either, so leave the promise out rather than filing one nothing could ever fail.\n- One leastSure sentence, optional, naming the single thing you are least sure of and what you did\n about it. The person reads that instead of the number.\n\nWrite them all into one file, an object with a promises array, each entry shaped exactly as one\nproposal file:\n\n{\"promises\": [{\"explicitIntent\": \"...\", \"teachBack\": {\"confidence\": 0.9, \"wrongOutcome\": \"...\", \"meaning\": {\"title\": \"...\"}}}]}\n\nAn entry carries explicitIntent and teachBack, and inside teachBack only meaning, confidence,\nwrongOutcome, leastSure, unresolvedQuestions, and proposedOwnerId. Anything else is refused on this\nmachine before the file leaves it, and one bad entry means not one of them is sent.\n\nEvery promise in the file is proposed as owned by whoever ran setup, and waits for that person. The\ncommand prints one link that opens all of them at once. Give the person that link and ask them to\nread each promise and click Agree. Nothing you can run agrees to one.\n\nFile the whole catalog in one go with the discover subcommand, naming that file:";
|