balladeer 1.0.15 → 1.0.16

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.
Files changed (65) hide show
  1. package/README.md +2 -0
  2. package/dist/behavior-map-schema.d.ts +107 -0
  3. package/dist/behavior-map-schema.js +232 -0
  4. package/dist/cli.d.ts +5 -0
  5. package/dist/cli.js +55 -0
  6. package/dist/commands/judge.d.ts +271 -0
  7. package/dist/commands/judge.js +1170 -0
  8. package/dist/commands/map.d.ts +120 -0
  9. package/dist/commands/map.js +601 -0
  10. package/dist/commands/offers.d.ts +265 -0
  11. package/dist/commands/offers.js +801 -0
  12. package/dist/commands/risk.d.ts +29 -0
  13. package/dist/commands/risk.js +133 -0
  14. package/dist/copy.d.ts +24 -1
  15. package/dist/copy.js +91 -0
  16. package/dist/guidance-hook.mjs +71 -1
  17. package/dist/headless-agent.d.ts +166 -0
  18. package/dist/headless-agent.js +416 -0
  19. package/dist/judge-brief.d.ts +36 -0
  20. package/dist/judge-brief.js +89 -0
  21. package/dist/judge-hook.d.ts +84 -0
  22. package/dist/judge-hook.js +420 -0
  23. package/dist/map-brief.d.ts +16 -0
  24. package/dist/map-brief.js +41 -0
  25. package/dist/offer-brief.d.ts +95 -0
  26. package/dist/offer-brief.js +217 -0
  27. package/dist/owned-process.d.ts +65 -0
  28. package/dist/owned-process.js +146 -0
  29. package/dist/promise-meaning.d.ts +126 -0
  30. package/dist/promise-meaning.js +292 -0
  31. package/dist/risk/contract.d.ts +145 -0
  32. package/dist/risk/contract.js +74 -0
  33. package/dist/risk/describe.d.ts +7 -0
  34. package/dist/risk/describe.js +45 -0
  35. package/dist/risk/diff.d.ts +26 -0
  36. package/dist/risk/diff.js +174 -0
  37. package/dist/risk/extract.d.ts +43 -0
  38. package/dist/risk/extract.js +336 -0
  39. package/dist/risk/git.d.ts +30 -0
  40. package/dist/risk/git.js +118 -0
  41. package/dist/risk/import-graph.d.ts +41 -0
  42. package/dist/risk/import-graph.js +487 -0
  43. package/dist/risk/index.d.ts +20 -0
  44. package/dist/risk/index.js +20 -0
  45. package/dist/risk/paths.d.ts +16 -0
  46. package/dist/risk/paths.js +73 -0
  47. package/dist/risk/pipeline.d.ts +47 -0
  48. package/dist/risk/pipeline.js +121 -0
  49. package/dist/risk/priors.d.ts +18 -0
  50. package/dist/risk/priors.js +86 -0
  51. package/dist/risk/resources.d.ts +56 -0
  52. package/dist/risk/resources.js +374 -0
  53. package/dist/risk/score.d.ts +85 -0
  54. package/dist/risk/score.js +543 -0
  55. package/dist/risk/symbols.d.ts +35 -0
  56. package/dist/risk/symbols.js +348 -0
  57. package/dist/risk/text.d.ts +43 -0
  58. package/dist/risk/text.js +277 -0
  59. package/dist/risk/validate.d.ts +19 -0
  60. package/dist/risk/validate.js +154 -0
  61. package/dist/scratch-worktree.d.ts +51 -0
  62. package/dist/scratch-worktree.js +153 -0
  63. package/dist/wire.d.ts +52 -3
  64. package/dist/wire.js +2 -2
  65. package/package.json +1 -1
@@ -0,0 +1,29 @@
1
+ import { type RiskReport } from "../risk/index.js";
2
+ /**
3
+ * `balladeer risk`: which promises the change in front of you could put at
4
+ * risk, ranked, with the evidence for each.
5
+ *
6
+ * A thin wrapper over `../risk/`. It reads the behavior maps committed under
7
+ * `.continuity/behavior-maps/`, reads the change from git, and scores it on
8
+ * this machine. No model runs and nothing is sent anywhere: the maps, the diff
9
+ * and the report all name the customer's own code.
10
+ */
11
+ export type RiskOptions = Readonly<{
12
+ cwd: string;
13
+ json: boolean;
14
+ base?: string;
15
+ head?: string;
16
+ /** Where the maps are, when not in `.continuity/behavior-maps/`. */
17
+ maps?: string;
18
+ write: (text: string) => void;
19
+ error?: (text: string) => void;
20
+ }>;
21
+ /**
22
+ * Where the change starts when nobody says: the merge base with this branch's
23
+ * upstream, which is what a push would send; else with origin/main; else the
24
+ * commit before head.
25
+ */
26
+ export declare function defaultBase(root: string, head: string): string;
27
+ /** The ranked table a person reads. */
28
+ export declare function formatRiskReport(report: RiskReport, changedFiles: number, unreadableMaps?: number): string[];
29
+ export declare function runRisk(options: RiskOptions): Promise<number>;
@@ -0,0 +1,133 @@
1
+ import { isAbsolute, resolve } from "node:path";
2
+ import { assessChange, BEHAVIOR_MAP_DIRECTORY, describeReason, GitReadError, readBehaviorMaps, tryGit, } from "../risk/index.js";
3
+ import { repositoryRoot } from "../git.js";
4
+ import { readLocalPackages } from "../touch-map.js";
5
+ import { CLI_INVOCATION } from "../wire.js";
6
+ /**
7
+ * Where the change starts when nobody says: the merge base with this branch's
8
+ * upstream, which is what a push would send; else with origin/main; else the
9
+ * commit before head.
10
+ */
11
+ export function defaultBase(root, head) {
12
+ const upstream = tryGit(root, [
13
+ "rev-parse",
14
+ "--abbrev-ref",
15
+ "--symbolic-full-name",
16
+ "@{upstream}",
17
+ ])?.trim();
18
+ for (const ref of [upstream, "origin/main"]) {
19
+ if (ref === undefined || ref.length === 0)
20
+ continue;
21
+ const base = tryGit(root, ["merge-base", head, ref])?.trim();
22
+ if (base !== undefined && /^[0-9a-f]{40}$/.test(base))
23
+ return base;
24
+ }
25
+ return `${head}~1`;
26
+ }
27
+ const BAND_LABEL = {
28
+ high: "high",
29
+ medium: "medium",
30
+ low: "low",
31
+ none: "none",
32
+ unmapped: "no map",
33
+ };
34
+ function short(sha) {
35
+ return sha.slice(0, 8);
36
+ }
37
+ /** The ranked table a person reads. */
38
+ export function formatRiskReport(report, changedFiles, unreadableMaps = 0) {
39
+ const lines = [];
40
+ lines.push(`Promises this change could put at risk (${changedFiles === 1 ? "1 file" : `${changedFiles} files`}, ${short(report.base)} to ${short(report.head)})`);
41
+ const ranked = report.promises.filter((entry) => entry.band !== "none" && entry.band !== "unmapped");
42
+ const quiet = report.promises.filter((entry) => entry.band === "none");
43
+ const unmapped = report.promises.filter((entry) => entry.band === "unmapped");
44
+ if (report.promises.length === 0) {
45
+ lines.push("");
46
+ lines.push(unreadableMaps > 0
47
+ ? `No behavior map in ${BEHAVIOR_MAP_DIRECTORY} could be read, so there is nothing to rank. Build them again with: ${CLI_INVOCATION} map <promise id>`
48
+ : `There are no behavior maps in ${BEHAVIOR_MAP_DIRECTORY} yet, so there is nothing to rank. Build one with: ${CLI_INVOCATION} map <promise id>`);
49
+ return lines;
50
+ }
51
+ lines.push("");
52
+ if (ranked.length === 0)
53
+ lines.push("No mapped promise shows any sign of this change.");
54
+ for (const entry of ranked) {
55
+ const band = BAND_LABEL[entry.band].padEnd(7);
56
+ const score = entry.score.toFixed(2);
57
+ lines.push(` ${band} ${score} ${entry.promiseId}${entry.title ? ` ${entry.title}` : ""}`);
58
+ const top = entry.reasons[0];
59
+ if (top !== undefined)
60
+ lines.push(` ${describeReason(top)}`);
61
+ const next = entry.reasons[1];
62
+ if (next !== undefined)
63
+ lines.push(` ${describeReason(next)}`);
64
+ }
65
+ if (quiet.length > 0) {
66
+ lines.push("");
67
+ lines.push(`No sign of this change in ${quiet.length === 1 ? "1 other mapped promise" : `${quiet.length} other mapped promises`}: ${quiet
68
+ .map((entry) => entry.promiseId)
69
+ .join(", ")}`);
70
+ }
71
+ if (unmapped.length > 0) {
72
+ lines.push("");
73
+ lines.push(`Not ranked, because they have no behavior map yet: ${unmapped.map((entry) => entry.promiseId).join(", ")}`);
74
+ lines.push(`Build one with: ${CLI_INVOCATION} map <promise id>`);
75
+ }
76
+ lines.push("");
77
+ lines.push("A band says where to look first. It is not a verdict on whether a promise still holds.");
78
+ lines.push("Worked out on this machine from your checkout. Balladeer was not asked, and was told nothing.");
79
+ return lines;
80
+ }
81
+ export async function runRisk(options) {
82
+ const error = options.error ?? ((text) => void process.stderr.write(text));
83
+ const fail = (reason, message) => {
84
+ if (options.json) {
85
+ const step = { step: "error", reason, message, changed: false, exitCode: 4 };
86
+ options.write(`${JSON.stringify(step)}\n`);
87
+ }
88
+ else
89
+ options.write(`${message}\n`);
90
+ return 4;
91
+ };
92
+ const root = await repositoryRoot(options.cwd);
93
+ if (root === undefined)
94
+ return fail("not_a_repository", "This is not a git repository, so there is no change to read. Run it from inside your checkout.");
95
+ const directory = options.maps === undefined
96
+ ? resolve(root, BEHAVIOR_MAP_DIRECTORY)
97
+ : isAbsolute(options.maps)
98
+ ? options.maps
99
+ : resolve(options.cwd, options.maps);
100
+ const { maps, problems } = readBehaviorMaps(directory);
101
+ for (const problem of problems)
102
+ error(`Could not read the behavior map ${problem.file}, so it is left out: ${problem.message}\n`);
103
+ // Promises this checkout knows about without asking anybody: the sealed
104
+ // packages it carries. Those without a map are listed as having none.
105
+ const known = options.maps === undefined ? readLocalPackages(root).map((pkg) => pkg.promiseId) : [];
106
+ const head = options.head ?? "HEAD";
107
+ const base = options.base ?? defaultBase(root, head);
108
+ try {
109
+ const { report, extraction } = await assessChange({
110
+ root,
111
+ base,
112
+ head,
113
+ maps,
114
+ promiseIds: known,
115
+ });
116
+ if (options.json) {
117
+ options.write(`${JSON.stringify(report)}\n`);
118
+ return 0;
119
+ }
120
+ if (extraction.files.length === 0) {
121
+ options.write(`Nothing changed between ${short(report.base)} and ${short(report.head)}. Name where the change starts with --base, for example --base origin/main.\n`);
122
+ return 0;
123
+ }
124
+ for (const line of formatRiskReport(report, extraction.files.length, problems.length))
125
+ options.write(`${line}\n`);
126
+ return 0;
127
+ }
128
+ catch (caught) {
129
+ if (caught instanceof GitReadError)
130
+ return fail("unreadable_change", `Could not read the change from ${base} to ${head}: ${caught.message}. Name both ends with --base and --head.`);
131
+ throw caught;
132
+ }
133
+ }
package/dist/copy.d.ts CHANGED
@@ -115,6 +115,29 @@ export declare const CAPTURE_SEAM = "### When to offer, and what a yes is worth\
115
115
  * initialize, and the page `/agent` serves. A packaging test holds them
116
116
  * together.
117
117
  */
118
+ /**
119
+ * What a check has to look like to still be worth having after the code under
120
+ * it is rewritten.
121
+ *
122
+ * Brian, one of the most active users, said on 28 September 2026 that the
123
+ * checks agents build read like scripts: deterministic, and brittle. Every
124
+ * earlier instruction said build the verifier and seal it, and nothing said
125
+ * against what. The cheapest way to satisfy four controls is to copy the
126
+ * product's logic into a stand-in and pin its exact output, which passes
127
+ * forever and catches nothing. This is the against-what, at the moment an
128
+ * agent is about to write one, and three carriers say it byte for byte.
129
+ */
130
+ export declare const VERIFIER_SHAPE = "### The shape of a check that lasts\n\nWrite a test only when the promise is easily testable: the repository's own test runner reaches the\nbehavior in the ordinary way, its existing fixtures set the situation up, no outside system is\nneeded, and the assertion is one of the promise's cases. Put it in the team's own suite, under the\nteam's runner, with a comment naming the promise id, so a later judge can find it, read it and run\nit first. If reaching the behavior would mean stand-in modules, a scenario repository or a copy of\nthe product's logic, the promise is not easily testable: write nothing and say so. The pre-push\njudge below covers it.\n\nA check exists to catch the failure its owner named, and to keep catching it after the code\nunderneath has been rewritten by people who never read the promise. So build it against the\nbehavior, at the boundary the promise names: the request a caller sends, the command a person runs,\nthe row that ends up written, the page that renders. Reach the product through the entry point its\nusers reach it through, never through a helper you found while reading the source, and never through\na copy of the product's logic written into the check. A copy passes forever while the product drifts\nunderneath it.\n\nAssert what the promise's cases say and nothing more. Each passing case states a property; check\nthat property. Each failing case names an outcome that must not happen; make the check refuse\nexactly that. Do not pin whole outputs, orderings the promise lets vary, or values the owner never\nmentioned: every one of those becomes a red the owner is told to act on when the behavior has not\nbroken.\n\nThe refactor control is where a brittle check shows itself. It must be a genuine restructuring of\nthe same behavior, moved, renamed or reimplemented, with the same observable outcome, reached\nthrough the same interface the check uses to reach the product. If you cannot write it without also\nediting the check, the check is bound to an implementation rather than to the promise, and what\nneeds fixing is the check. The known-bad control must be the failure the owner wrote down, not the\ncheck sabotaging itself.\n\nWhere the repository's own test runner already reaches the behavior, run it through the declared\nresult protocol rather than writing a second harness: the existing suite is the one the team will\nkeep maintaining. And when a check cannot reach a verdict, say so. A check that could not run reads\nUnknown, which is honest, and never as a pass, which is not.";
131
+ /**
132
+ * Brian's mechanism, in the shape that needs no customer model key: the judge
133
+ * is a sub-agent of the coding agent the customer is already running, spawned
134
+ * before the push with the promise, the changed files and a fixed brief, and
135
+ * held to verdict rules that make its answer worth something. Robert's
136
+ * constraint of 28 September 2026: nothing in CI and nothing on Balladeer's side
137
+ * may need a key of theirs. None of this is protection; it is the check the
138
+ * agent runs so that CI has nothing to find.
139
+ */
140
+ export declare const PRE_PUSH_JUDGE = "### Before you push, try to break it\n\nThe sealed check runs in CI, after the fact. The cheapest moment to catch a broken promise is before\nthe push, on the machine where the change can still be fixed, and the way to catch it is not to\nreread your own diff. Find the promises the change touches: balladeer affected <paths> where this\nrepository has a touch map, otherwise the leads discover_intent gave you for those paths. For each\none that has a linked test or a sealed check, run it first; a test named for the promise is the\nfastest verdict there is, and the runner's run-one-target command prints the four facts a red check\ncarries.\n\nThen, for every promise the change touches, checked or not, spawn a fresh agent that has not seen\nyour change or your reasoning, however your host spawns one, and hand it three things and nothing\nelse: the promise as get_promise returns it, the changed files, and the brief below. Not the\npull-request description and not your notes. One agent per promise; a judge handed six promises at\nonce holds none of them. If your host cannot spawn one, work through the brief yourself in a fresh\ncontext after the work is finished, and say that you did.\n\nThe brief, verbatim: \"You are checking one promise against one change. Read the promise's cases\nfirst. Then find out, by running things, whether the change keeps it. You may report broken only\nwith a reproduction: a command or test that fails with this change and passes without it, which\nyou have run both ways. You may report kept only after you have exercised each of the promise's\npassing cases against the changed code. Anything else you report as could not tell, with what\nstopped you. Do not take the author's word for anything, read code rather than descriptions, and\nname the promise, never the person.\"\n\nWhat to do with the answer. Broken, with a reproduction: fix the behavior before pushing, keep the\nreproduction, and offer to carry it into the sealed check as a failing case, which means re-sealing\nand re-qualifying, so ask before you do. Kept: say so in one line, naming the promise. Could not\ntell: say that, in those words; it is not a pass. None of this is protection, and none of it reaches\nBalladeer on its own. Protection is the sealed check qualifying in CI. This is the check you run so\nthat CI has nothing to find.";
118
141
  export declare const SETTLING_QUESTIONS = "### When somebody says \"tell me about\" one\n\nAn id is how a person points at something here. Expand a promise id with get_promise and a proposal\nid with get_proposal. Describe who it is for, when it applies, the behavior and the concrete cases.\nA proposal awaits its named owner's agreement; a promise is agreed and may still be unprotected.\nPreviously recorded answers are historical reasoning, not a new interview or an approval.\nIf the person wants a change, clarify the changed commitment before writing it. Preserve all other\nmeaning and cases. An agreed promise changes only through a revision its named owner approves.";
119
142
  /**
120
143
  * What a session may spend on reading, in three sentences.
@@ -193,7 +216,7 @@ export declare const SESSION_STAMP = "Use the installed `balladeer` command afte
193
216
  * block that drifts per run would rewrite a customer's committed file on every
194
217
  * setup and the diff would say nothing.
195
218
  */
196
- export declare const WORKFLOW_GUIDANCE = "## 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 to offer, and what a yes is worth\n\nNotice durable customer behavior during ordinary work. Offer to preserve a concrete behavior\nbeside useful work, without blocking implementation. File nothing until the person explicitly\nasks you to record it or accepts a scoped capture offer. A factual answer or permission to build\nalone is not permission to record. A no ends that offer; do not raise it again in this conversation.\n\nDo not offer while the user is comparing options or exploring without a decision. If they later\nchoose a concrete behavior, you may offer to capture it even though the conversation began as\nexploration. Do not offer new promises for pure refactors or cosmetic changes. During an active\nproduction incident, prioritize recovery and defer capture until recovery. Ordinary bug fixing\nis not automatically an active production incident.\n\nFor clear feature intent, an offer before implementation can make the promise a target for the\nwork. For a normal bug fix, offer alongside the completed fix and checks. Neither requires a\nmerge or another human closing reply. If the user is away, one nonblocking offer can remain in\nyour completion message for their return; do not file or wait for an answer.\n\nUse available context to state a specific behavior. A precise capture offer can confirm your\ninterpretation and obtain permission to record in one yes. Ask a separate work question only\nwhen a consequential choice remains that a clear, scoped offer cannot resolve. An unanswered\nearlier question is not itself a reason to ask again. The server supplies current detailed guidance.\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\nUse the installed `balladeer` command after setup reports CLI readiness. If it is missing from\nPATH (including an older 1.0.4 setup), use `npx -y balladeer@latest <command>` for that invocation\nand read the current /agent instructions for installation or repair. Do not claim that an older\nrepository's instructions were refreshed. CLI availability does not prove that this coding host\nloaded the native MCP tools or that a credential is authorized. The optional session stamp below\nis separate from all three.\n\nRun `balladeer 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 `balladeer 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\nA request to build a behavior is not permission to record it. An explicit request to preserve it\nor acceptance of a specific capture offer permits one pending proposal for that behavior, not\nother behaviors you notice. A factual answer alone does not authorize recording.\n\nUse available conversation and repository context before asking questions. A precise capture\noffer can state your interpretation and obtain permission to record it in one yes. Ask separately\nonly when a consequential choice remains that the scoped offer cannot resolve. Do not ask for\nknown facts, repeat a question resolved by later context or acceptance, or require a formal\nmust-not sentence from the user. Preserve their actual words separately from your interpretation.\nIf no specific commitment is possible yet, continue the authorized work without filing it.\n\nUse relevant existing promises already retrieved, or the bounded repository/path lookup, to notice\nconcrete conflicts before writing. If the same situation would require incompatible outcomes, show\nthe two commitments and ask which behavior they intend. Revising or superseding agreed meaning\nstill requires its named human owner. There is no automatic catalog-conflict gate or stored question\nbacklog in Balladeer. Unrelated uncertainties need not delay capture. No questions are required when\nthe commitment is already clear.\n\nPreserve the person's actual words. Every failure they named belongs in the failing examples.\nYou may derive concrete cases within the behavior they requested or confirmed in a precise\ncapture offer; they need not dictate every example. Trace each case to the actual request and\nconfirmed scope. Keep literal human source in saidWords, and your offer plus their acceptance in\nexplicitIntent. Never quote your own wording as theirs or add unsupported policy, thresholds,\nroles, systems or guarantees. If a case requires an unresolved product choice, clarify that choice\ninstead of guessing or asking the person to enumerate routine examples.\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, and the numbered cases that must keep working and the ones that must be caught. Do not\nstore or present an unanswered-question backlog. 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 `balladeer 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`balladeer 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. Expand a promise id with get_promise and a proposal\nid with get_proposal. Describe who it is for, when it applies, the behavior and the concrete cases.\nA proposal awaits its named owner's agreement; a promise is agreed and may still be unprotected.\nPreviously recorded answers are historical reasoning, not a new interview or an approval.\nIf the person wants a change, clarify the changed commitment before writing it. Preserve all other\nmeaning and cases. An agreed promise changes only through a revision its named owner approves.\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\nballadeer 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.";
219
+ export declare const WORKFLOW_GUIDANCE = "## 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 to offer, and what a yes is worth\n\nNotice durable customer behavior during ordinary work. Offer to preserve a concrete behavior\nbeside useful work, without blocking implementation. File nothing until the person explicitly\nasks you to record it or accepts a scoped capture offer. A factual answer or permission to build\nalone is not permission to record. A no ends that offer; do not raise it again in this conversation.\n\nDo not offer while the user is comparing options or exploring without a decision. If they later\nchoose a concrete behavior, you may offer to capture it even though the conversation began as\nexploration. Do not offer new promises for pure refactors or cosmetic changes. During an active\nproduction incident, prioritize recovery and defer capture until recovery. Ordinary bug fixing\nis not automatically an active production incident.\n\nFor clear feature intent, an offer before implementation can make the promise a target for the\nwork. For a normal bug fix, offer alongside the completed fix and checks. Neither requires a\nmerge or another human closing reply. If the user is away, one nonblocking offer can remain in\nyour completion message for their return; do not file or wait for an answer.\n\nUse available context to state a specific behavior. A precise capture offer can confirm your\ninterpretation and obtain permission to record in one yes. Ask a separate work question only\nwhen a consequential choice remains that a clear, scoped offer cannot resolve. An unanswered\nearlier question is not itself a reason to ask again. The server supplies current detailed guidance.\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\nUse the installed `balladeer` command after setup reports CLI readiness. If it is missing from\nPATH (including an older 1.0.4 setup), use `npx -y balladeer@latest <command>` for that invocation\nand read the current /agent instructions for installation or repair. Do not claim that an older\nrepository's instructions were refreshed. CLI availability does not prove that this coding host\nloaded the native MCP tools or that a credential is authorized. The optional session stamp below\nis separate from all three.\n\nRun `balladeer 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 `balladeer 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\nA request to build a behavior is not permission to record it. An explicit request to preserve it\nor acceptance of a specific capture offer permits one pending proposal for that behavior, not\nother behaviors you notice. A factual answer alone does not authorize recording.\n\nUse available conversation and repository context before asking questions. A precise capture\noffer can state your interpretation and obtain permission to record it in one yes. Ask separately\nonly when a consequential choice remains that the scoped offer cannot resolve. Do not ask for\nknown facts, repeat a question resolved by later context or acceptance, or require a formal\nmust-not sentence from the user. Preserve their actual words separately from your interpretation.\nIf no specific commitment is possible yet, continue the authorized work without filing it.\n\nUse relevant existing promises already retrieved, or the bounded repository/path lookup, to notice\nconcrete conflicts before writing. If the same situation would require incompatible outcomes, show\nthe two commitments and ask which behavior they intend. Revising or superseding agreed meaning\nstill requires its named human owner. There is no automatic catalog-conflict gate or stored question\nbacklog in Balladeer. Unrelated uncertainties need not delay capture. No questions are required when\nthe commitment is already clear.\n\nPreserve the person's actual words. Every failure they named belongs in the failing examples.\nYou may derive concrete cases within the behavior they requested or confirmed in a precise\ncapture offer; they need not dictate every example. Trace each case to the actual request and\nconfirmed scope. Keep literal human source in saidWords, and your offer plus their acceptance in\nexplicitIntent. Never quote your own wording as theirs or add unsupported policy, thresholds,\nroles, systems or guarantees. If a case requires an unresolved product choice, clarify that choice\ninstead of guessing or asking the person to enumerate routine examples.\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, and the numbered cases that must keep working and the ones that must be caught. Do not\nstore or present an unanswered-question backlog. 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 `balladeer 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`balladeer 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### The shape of a check that lasts\n\nWrite a test only when the promise is easily testable: the repository's own test runner reaches the\nbehavior in the ordinary way, its existing fixtures set the situation up, no outside system is\nneeded, and the assertion is one of the promise's cases. Put it in the team's own suite, under the\nteam's runner, with a comment naming the promise id, so a later judge can find it, read it and run\nit first. If reaching the behavior would mean stand-in modules, a scenario repository or a copy of\nthe product's logic, the promise is not easily testable: write nothing and say so. The pre-push\njudge below covers it.\n\nA check exists to catch the failure its owner named, and to keep catching it after the code\nunderneath has been rewritten by people who never read the promise. So build it against the\nbehavior, at the boundary the promise names: the request a caller sends, the command a person runs,\nthe row that ends up written, the page that renders. Reach the product through the entry point its\nusers reach it through, never through a helper you found while reading the source, and never through\na copy of the product's logic written into the check. A copy passes forever while the product drifts\nunderneath it.\n\nAssert what the promise's cases say and nothing more. Each passing case states a property; check\nthat property. Each failing case names an outcome that must not happen; make the check refuse\nexactly that. Do not pin whole outputs, orderings the promise lets vary, or values the owner never\nmentioned: every one of those becomes a red the owner is told to act on when the behavior has not\nbroken.\n\nThe refactor control is where a brittle check shows itself. It must be a genuine restructuring of\nthe same behavior, moved, renamed or reimplemented, with the same observable outcome, reached\nthrough the same interface the check uses to reach the product. If you cannot write it without also\nediting the check, the check is bound to an implementation rather than to the promise, and what\nneeds fixing is the check. The known-bad control must be the failure the owner wrote down, not the\ncheck sabotaging itself.\n\nWhere the repository's own test runner already reaches the behavior, run it through the declared\nresult protocol rather than writing a second harness: the existing suite is the one the team will\nkeep maintaining. And when a check cannot reach a verdict, say so. A check that could not run reads\nUnknown, which is honest, and never as a pass, which is not.\n\n### Before you push, try to break it\n\nThe sealed check runs in CI, after the fact. The cheapest moment to catch a broken promise is before\nthe push, on the machine where the change can still be fixed, and the way to catch it is not to\nreread your own diff. Find the promises the change touches: balladeer affected <paths> where this\nrepository has a touch map, otherwise the leads discover_intent gave you for those paths. For each\none that has a linked test or a sealed check, run it first; a test named for the promise is the\nfastest verdict there is, and the runner's run-one-target command prints the four facts a red check\ncarries.\n\nThen, for every promise the change touches, checked or not, spawn a fresh agent that has not seen\nyour change or your reasoning, however your host spawns one, and hand it three things and nothing\nelse: the promise as get_promise returns it, the changed files, and the brief below. Not the\npull-request description and not your notes. One agent per promise; a judge handed six promises at\nonce holds none of them. If your host cannot spawn one, work through the brief yourself in a fresh\ncontext after the work is finished, and say that you did.\n\nThe brief, verbatim: \"You are checking one promise against one change. Read the promise's cases\nfirst. Then find out, by running things, whether the change keeps it. You may report broken only\nwith a reproduction: a command or test that fails with this change and passes without it, which\nyou have run both ways. You may report kept only after you have exercised each of the promise's\npassing cases against the changed code. Anything else you report as could not tell, with what\nstopped you. Do not take the author's word for anything, read code rather than descriptions, and\nname the promise, never the person.\"\n\nWhat to do with the answer. Broken, with a reproduction: fix the behavior before pushing, keep the\nreproduction, and offer to carry it into the sealed check as a failing case, which means re-sealing\nand re-qualifying, so ask before you do. Kept: say so in one line, naming the promise. Could not\ntell: say that, in those words; it is not a pass. None of this is protection, and none of it reaches\nBalladeer on its own. Protection is the sealed check qualifying in CI. This is the check you run so\nthat CI has nothing to find.\n\n### When somebody says \"tell me about\" one\n\nAn id is how a person points at something here. Expand a promise id with get_promise and a proposal\nid with get_proposal. Describe who it is for, when it applies, the behavior and the concrete cases.\nA proposal awaits its named owner's agreement; a promise is agreed and may still be unprotected.\nPreviously recorded answers are historical reasoning, not a new interview or an approval.\nIf the person wants a change, clarify the changed commitment before writing it. Preserve all other\nmeaning and cases. An agreed promise changes only through a revision its named owner approves.\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\nballadeer 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.";
197
220
  /**
198
221
  * How an agent turns a repository that has just been connected into a catalog a
199
222
  * person can read. Setup's last step prints it, and the control plane's `/agent`
package/dist/copy.js CHANGED
@@ -276,6 +276,93 @@ Then take the customer-visible half if they give you one, and file that instead.
276
276
  * initialize, and the page `/agent` serves. A packaging test holds them
277
277
  * together.
278
278
  */
279
+ /**
280
+ * What a check has to look like to still be worth having after the code under
281
+ * it is rewritten.
282
+ *
283
+ * Brian, one of the most active users, said on 28 September 2026 that the
284
+ * checks agents build read like scripts: deterministic, and brittle. Every
285
+ * earlier instruction said build the verifier and seal it, and nothing said
286
+ * against what. The cheapest way to satisfy four controls is to copy the
287
+ * product's logic into a stand-in and pin its exact output, which passes
288
+ * forever and catches nothing. This is the against-what, at the moment an
289
+ * agent is about to write one, and three carriers say it byte for byte.
290
+ */
291
+ export const VERIFIER_SHAPE = `### The shape of a check that lasts
292
+
293
+ Write a test only when the promise is easily testable: the repository's own test runner reaches the
294
+ behavior in the ordinary way, its existing fixtures set the situation up, no outside system is
295
+ needed, and the assertion is one of the promise's cases. Put it in the team's own suite, under the
296
+ team's runner, with a comment naming the promise id, so a later judge can find it, read it and run
297
+ it first. If reaching the behavior would mean stand-in modules, a scenario repository or a copy of
298
+ the product's logic, the promise is not easily testable: write nothing and say so. The pre-push
299
+ judge below covers it.
300
+
301
+ A check exists to catch the failure its owner named, and to keep catching it after the code
302
+ underneath has been rewritten by people who never read the promise. So build it against the
303
+ behavior, at the boundary the promise names: the request a caller sends, the command a person runs,
304
+ the row that ends up written, the page that renders. Reach the product through the entry point its
305
+ users reach it through, never through a helper you found while reading the source, and never through
306
+ a copy of the product's logic written into the check. A copy passes forever while the product drifts
307
+ underneath it.
308
+
309
+ Assert what the promise's cases say and nothing more. Each passing case states a property; check
310
+ that property. Each failing case names an outcome that must not happen; make the check refuse
311
+ exactly that. Do not pin whole outputs, orderings the promise lets vary, or values the owner never
312
+ mentioned: every one of those becomes a red the owner is told to act on when the behavior has not
313
+ broken.
314
+
315
+ The refactor control is where a brittle check shows itself. It must be a genuine restructuring of
316
+ the same behavior, moved, renamed or reimplemented, with the same observable outcome, reached
317
+ through the same interface the check uses to reach the product. If you cannot write it without also
318
+ editing the check, the check is bound to an implementation rather than to the promise, and what
319
+ needs fixing is the check. The known-bad control must be the failure the owner wrote down, not the
320
+ check sabotaging itself.
321
+
322
+ Where the repository's own test runner already reaches the behavior, run it through the declared
323
+ result protocol rather than writing a second harness: the existing suite is the one the team will
324
+ keep maintaining. And when a check cannot reach a verdict, say so. A check that could not run reads
325
+ Unknown, which is honest, and never as a pass, which is not.`;
326
+ /**
327
+ * Brian's mechanism, in the shape that needs no customer model key: the judge
328
+ * is a sub-agent of the coding agent the customer is already running, spawned
329
+ * before the push with the promise, the changed files and a fixed brief, and
330
+ * held to verdict rules that make its answer worth something. Robert's
331
+ * constraint of 28 September 2026: nothing in CI and nothing on Balladeer's side
332
+ * may need a key of theirs. None of this is protection; it is the check the
333
+ * agent runs so that CI has nothing to find.
334
+ */
335
+ export const PRE_PUSH_JUDGE = `### Before you push, try to break it
336
+
337
+ The sealed check runs in CI, after the fact. The cheapest moment to catch a broken promise is before
338
+ the push, on the machine where the change can still be fixed, and the way to catch it is not to
339
+ reread your own diff. Find the promises the change touches: balladeer affected <paths> where this
340
+ repository has a touch map, otherwise the leads discover_intent gave you for those paths. For each
341
+ one that has a linked test or a sealed check, run it first; a test named for the promise is the
342
+ fastest verdict there is, and the runner's run-one-target command prints the four facts a red check
343
+ carries.
344
+
345
+ Then, for every promise the change touches, checked or not, spawn a fresh agent that has not seen
346
+ your change or your reasoning, however your host spawns one, and hand it three things and nothing
347
+ else: the promise as get_promise returns it, the changed files, and the brief below. Not the
348
+ pull-request description and not your notes. One agent per promise; a judge handed six promises at
349
+ once holds none of them. If your host cannot spawn one, work through the brief yourself in a fresh
350
+ context after the work is finished, and say that you did.
351
+
352
+ The brief, verbatim: "You are checking one promise against one change. Read the promise's cases
353
+ first. Then find out, by running things, whether the change keeps it. You may report broken only
354
+ with a reproduction: a command or test that fails with this change and passes without it, which
355
+ you have run both ways. You may report kept only after you have exercised each of the promise's
356
+ passing cases against the changed code. Anything else you report as could not tell, with what
357
+ stopped you. Do not take the author's word for anything, read code rather than descriptions, and
358
+ name the promise, never the person."
359
+
360
+ What to do with the answer. Broken, with a reproduction: fix the behavior before pushing, keep the
361
+ reproduction, and offer to carry it into the sealed check as a failing case, which means re-sealing
362
+ and re-qualifying, so ask before you do. Kept: say so in one line, naming the promise. Could not
363
+ tell: say that, in those words; it is not a pass. None of this is protection, and none of it reaches
364
+ Balladeer on its own. Protection is the sealed check qualifying in CI. This is the check you run so
365
+ that CI has nothing to find.`;
279
366
  export const SETTLING_QUESTIONS = `### When somebody says "tell me about" one
280
367
 
281
368
  An id is how a person points at something here. Expand a promise id with get_promise and a proposal
@@ -497,6 +584,10 @@ especially: it is Balladeer's word for a proposal, so it reads as jargon whateve
497
584
 
498
585
  ${SESSION_BEHAVIORS}
499
586
 
587
+ ${VERIFIER_SHAPE}
588
+
589
+ ${PRE_PUSH_JUDGE}
590
+
500
591
  ${SETTLING_QUESTIONS}
501
592
 
502
593
  ${SEALED_FILES}
@@ -2027,7 +2027,7 @@ var require_toml = __commonJS({
2027
2027
  });
2028
2028
 
2029
2029
  // src/wire.ts
2030
- var CLI_VERSION = "1.0.15";
2030
+ var CLI_VERSION = "1.0.16";
2031
2031
  var CLIENT_HEADER = "x-balladeer-client";
2032
2032
  var UPDATE_STATE_HEADER = "x-balladeer-update-state";
2033
2033
  var HOOK_HOST_HEADER = "x-balladeer-hook";
@@ -2740,6 +2740,72 @@ Balladeer only keeps promises a check can catch. If a customer would notice some
2740
2740
  slips, say that and I will keep that instead."
2741
2741
 
2742
2742
  Then take the customer-visible half if they give you one, and file that instead.`;
2743
+ var VERIFIER_SHAPE = `### The shape of a check that lasts
2744
+
2745
+ Write a test only when the promise is easily testable: the repository's own test runner reaches the
2746
+ behavior in the ordinary way, its existing fixtures set the situation up, no outside system is
2747
+ needed, and the assertion is one of the promise's cases. Put it in the team's own suite, under the
2748
+ team's runner, with a comment naming the promise id, so a later judge can find it, read it and run
2749
+ it first. If reaching the behavior would mean stand-in modules, a scenario repository or a copy of
2750
+ the product's logic, the promise is not easily testable: write nothing and say so. The pre-push
2751
+ judge below covers it.
2752
+
2753
+ A check exists to catch the failure its owner named, and to keep catching it after the code
2754
+ underneath has been rewritten by people who never read the promise. So build it against the
2755
+ behavior, at the boundary the promise names: the request a caller sends, the command a person runs,
2756
+ the row that ends up written, the page that renders. Reach the product through the entry point its
2757
+ users reach it through, never through a helper you found while reading the source, and never through
2758
+ a copy of the product's logic written into the check. A copy passes forever while the product drifts
2759
+ underneath it.
2760
+
2761
+ Assert what the promise's cases say and nothing more. Each passing case states a property; check
2762
+ that property. Each failing case names an outcome that must not happen; make the check refuse
2763
+ exactly that. Do not pin whole outputs, orderings the promise lets vary, or values the owner never
2764
+ mentioned: every one of those becomes a red the owner is told to act on when the behavior has not
2765
+ broken.
2766
+
2767
+ The refactor control is where a brittle check shows itself. It must be a genuine restructuring of
2768
+ the same behavior, moved, renamed or reimplemented, with the same observable outcome, reached
2769
+ through the same interface the check uses to reach the product. If you cannot write it without also
2770
+ editing the check, the check is bound to an implementation rather than to the promise, and what
2771
+ needs fixing is the check. The known-bad control must be the failure the owner wrote down, not the
2772
+ check sabotaging itself.
2773
+
2774
+ Where the repository's own test runner already reaches the behavior, run it through the declared
2775
+ result protocol rather than writing a second harness: the existing suite is the one the team will
2776
+ keep maintaining. And when a check cannot reach a verdict, say so. A check that could not run reads
2777
+ Unknown, which is honest, and never as a pass, which is not.`;
2778
+ var PRE_PUSH_JUDGE = `### Before you push, try to break it
2779
+
2780
+ The sealed check runs in CI, after the fact. The cheapest moment to catch a broken promise is before
2781
+ the push, on the machine where the change can still be fixed, and the way to catch it is not to
2782
+ reread your own diff. Find the promises the change touches: balladeer affected <paths> where this
2783
+ repository has a touch map, otherwise the leads discover_intent gave you for those paths. For each
2784
+ one that has a linked test or a sealed check, run it first; a test named for the promise is the
2785
+ fastest verdict there is, and the runner's run-one-target command prints the four facts a red check
2786
+ carries.
2787
+
2788
+ Then, for every promise the change touches, checked or not, spawn a fresh agent that has not seen
2789
+ your change or your reasoning, however your host spawns one, and hand it three things and nothing
2790
+ else: the promise as get_promise returns it, the changed files, and the brief below. Not the
2791
+ pull-request description and not your notes. One agent per promise; a judge handed six promises at
2792
+ once holds none of them. If your host cannot spawn one, work through the brief yourself in a fresh
2793
+ context after the work is finished, and say that you did.
2794
+
2795
+ The brief, verbatim: "You are checking one promise against one change. Read the promise's cases
2796
+ first. Then find out, by running things, whether the change keeps it. You may report broken only
2797
+ with a reproduction: a command or test that fails with this change and passes without it, which
2798
+ you have run both ways. You may report kept only after you have exercised each of the promise's
2799
+ passing cases against the changed code. Anything else you report as could not tell, with what
2800
+ stopped you. Do not take the author's word for anything, read code rather than descriptions, and
2801
+ name the promise, never the person."
2802
+
2803
+ What to do with the answer. Broken, with a reproduction: fix the behavior before pushing, keep the
2804
+ reproduction, and offer to carry it into the sealed check as a failing case, which means re-sealing
2805
+ and re-qualifying, so ask before you do. Kept: say so in one line, naming the promise. Could not
2806
+ tell: say that, in those words; it is not a pass. None of this is protection, and none of it reaches
2807
+ Balladeer on its own. Protection is the sealed check qualifying in CI. This is the check you run so
2808
+ that CI has nothing to find.`;
2743
2809
  var SETTLING_QUESTIONS = `### When somebody says "tell me about" one
2744
2810
 
2745
2811
  An id is how a person points at something here. Expand a promise id with get_promise and a proposal
@@ -2888,6 +2954,10 @@ especially: it is Balladeer's word for a proposal, so it reads as jargon whateve
2888
2954
 
2889
2955
  ${SESSION_BEHAVIORS}
2890
2956
 
2957
+ ${VERIFIER_SHAPE}
2958
+
2959
+ ${PRE_PUSH_JUDGE}
2960
+
2891
2961
  ${SETTLING_QUESTIONS}
2892
2962
 
2893
2963
  ${SEALED_FILES}