breakaway 1.4.0-main.52 → 1.4.0-main.54
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/package.json +2 -1
- package/prompts/breakaway.md +1 -1
- package/prompts/core.md +4 -4
- package/scripts/tasks.mjs +18 -1
- package/src/session-report.js +92 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "breakaway",
|
|
3
|
-
"version": "1.4.0-main.
|
|
3
|
+
"version": "1.4.0-main.54",
|
|
4
4
|
"description": "The task board for you and your coding agents: a Cloudflare Worker, its web app, Taskwarrior sync, and the CLI (npx breakaway).",
|
|
5
5
|
"license": "FSL-1.1-Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"src/prompt.js",
|
|
31
31
|
"src/redact.js",
|
|
32
32
|
"src/repos.js",
|
|
33
|
+
"src/session-report.js",
|
|
33
34
|
"src/specs.js",
|
|
34
35
|
"src/versions.js",
|
|
35
36
|
"prompts/*.md",
|
package/prompts/breakaway.md
CHANGED
|
@@ -25,7 +25,7 @@ The title is the work ID and a plain sentence (`BRK-12: Sort the inbox by age`),
|
|
|
25
25
|
|
|
26
26
|
## Direction
|
|
27
27
|
|
|
28
|
-
`AGENTS.md`'s "What breakaway is, and isn't" are settled: free and self-hosted, an install keeps its data, people merge and deploy, and the claims in `brand/README.md` stay true. An idea that breaks one needs the owner's decision first. Look at the board for the horizons (`tasks list --json`) and for tasks the work overlaps. For a technical change with real choices, settle it in a spec; for anything people will use, shape the flow and its empty, error, and first-run states in the spec too. Specs go in `docs/specs/<ID>-<slug>.md`: the problem, what you chose and why, what's out of scope, open questions,
|
|
28
|
+
`AGENTS.md`'s "What breakaway is, and isn't" are settled: free and self-hosted, an install keeps its data, people merge and deploy, and the claims in `brand/README.md` stay true. An idea that breaks one needs the owner's decision first. Look at the board for the horizons (`tasks list --json`) and for tasks the work overlaps. For a technical change with real choices, settle it in a spec; for anything people will use, shape the flow and its empty, error, and first-run states in the spec too. Specs go in `docs/specs/<ID>-<slug>.md`: the problem, what you chose and why, what's out of scope, open questions, done when, and how to check it, with status `draft`.
|
|
29
29
|
|
|
30
30
|
## Dependency updates
|
|
31
31
|
|
package/prompts/core.md
CHANGED
|
@@ -33,8 +33,8 @@ The idea is the task's description, in the owner's own words. Never rewrite it:
|
|
|
33
33
|
|
|
34
34
|
1. **Understand it.** If the payload has an `Attachments: <n>` line (or `show` lists images), look at the images first: `tasks attachments <the task> --save <a folder in your scratch space, not the repository>`, then `Read` each file. Each image's caption is what the owner wants you to notice. The line is context, like the owner's note. Read the idea and what the repository's **Direction** lists (its principles, settled decisions, and what it isn't doing), and use the skills it names for shaping. Look at the board (`tasks list --json`) for tasks that already cover part of it, tasks it overlaps with, and tasks it must wait for. Search the code for what already exists.
|
|
35
35
|
2. **Check it fits.** If the idea breaks a principle or a settled decision, or is something the repository isn't doing, don't turn it into agent work. Write the spec so it says so plainly, and ask the owner the question with a decision (see "Asking for a decision" below).
|
|
36
|
-
3. **Check in, then write the spec.** Check in on the peloton (step 4 above) with the spec and the areas the idea touches, before you write anything. Write the spec where the repository's **Direction** says specs go (as `<IDEA-ID>-<slug>.md`), from its template, with status `draft`. Say what the idea became, what you chose and why, what's out of scope, and any questions you couldn't settle. Describe in words what the images show where the spec needs them, since the images stay on the board; don't copy them into the repository unless the owner asks, and never repeat what the repository's **Never share** lists. Keep it as short as the idea allows. A small idea gets a short spec.
|
|
37
|
-
4. **Make the tasks.** One task per piece that one agent can finish in one pull request, with `tasks add "<title>" --project <area> --horizon <now|next|later> --tag agent|owner|decide --depends <IDs> --brief "<what and why>" --done-when "<what has to be true to call it done>"
|
|
36
|
+
3. **Check in, then write the spec.** Check in on the peloton (step 4 above) with the spec and the areas the idea touches, before you write anything. Write the spec where the repository's **Direction** says specs go (as `<IDEA-ID>-<slug>.md`), from its template, with status `draft`. Say what the idea became, what you chose and why, what's out of scope, and any questions you couldn't settle, and end it with **How to check it**: a few steps someone who isn't technical can follow, once the work is built, to see the result is what they asked for (what to open, what to do, what they should see). Describe in words what the images show where the spec needs them, since the images stay on the board; don't copy them into the repository unless the owner asks, and never repeat what the repository's **Never share** lists. Keep it as short as the idea allows. A small idea gets a short spec.
|
|
37
|
+
4. **Make the tasks.** One task per piece that one agent can finish in one pull request, with `tasks add "<title>" --project <area> --horizon <now|next|later> --tag agent|owner|decide --depends <IDs> --brief "<what and why>" --done-when "<what has to be true to call it done>"`, a done when the owner can check in a few minutes. They go in your checkout's repository; a piece that belongs in another repository is a task there (`--repo <slug>` on `add`), named in the spec. Fill in every field on purpose:
|
|
38
38
|
- the area that fits the rest of the board (look at neighbouring tasks), and a priority only when the idea says it matters;
|
|
39
39
|
- the horizon: if the idea has a tag `horizon-now`, `horizon-next`, or `horizon-later`, that's the owner's choice, so give every task exactly that horizon, and never change the tag. Only with `horizon-auto` do you choose, task by task, from the neighbouring tasks and the repository's horizons;
|
|
40
40
|
- `--tag agent` for work an agent can do in the repository, `--tag owner` for production, dashboards, accounts, and sign-offs, and `--tag decide` when the owner has to choose first;
|
|
@@ -42,7 +42,7 @@ The idea is the task's description, in the owner's own words. Never rewrite it:
|
|
|
42
42
|
- `--spec <path>` on the main task;
|
|
43
43
|
- one feature tag on every task, not a release tag: if the idea's tasks belong together, add the feature first (`tasks features add <slug> --title "<name>"`, with no release: aiming it at one is the owner's) and give each task `--tag <slug>`; if they join a feature already on the board (`tasks features`), use its slug.
|
|
44
44
|
Never set `--autostart`, never start an agent or a chase on a task or feature you made. Whether a task starts by itself is the owner's choice, made on the board, and the idea's own setting is not yours to copy.
|
|
45
|
-
5. **Hand over.** On the idea, `comment` the IDs you made and what each waits for, and `modify` nothing else about it (not its description). Open the pull request as the repository's **Pull requests** says: the title is `<IDEA-ID>: Shape <the idea in a few words>`, the description lists the new tasks and their blockers,
|
|
45
|
+
5. **Hand over.** On the idea, `comment` the IDs you made and what each waits for, and `modify` nothing else about it (not its description). Open the pull request as the repository's **Pull requests** says: the title is `<IDEA-ID>: Shape <the idea in a few words>`, the description lists the new tasks and their blockers, repeats the spec's **How to check it** under a `## How to check it` heading, and ends with "Closes <IDEA-ID>." Then `modify <IDEA-ID> --pr <number>` and keep watching the pull request as in step 8.
|
|
46
46
|
|
|
47
47
|
The pull request holds only the spec. The tasks already exist on the board, waiting for it to merge.
|
|
48
48
|
|
|
@@ -58,7 +58,7 @@ The owner kicked off a new project from the board: the task is its `IDEA-`, tagg
|
|
|
58
58
|
|
|
59
59
|
Write every `prompt` in everyday words, one idea per question, with options rather than open text where they work and your recommendation first, marked "(recommended)". A technical term goes only in `help`, explained in a line. Don't ask what the pitch already answers. Then `comment` what you asked and why, `release <the task>`, and stop: the owner answers on the board, and their **Send answers and carry on** starts the next run.
|
|
60
60
|
3. **Ask once more, only if you must.** If something important is still open after the first round's answers, ask a second round about only that, at most 6 questions, the same way, and stop. Two rounds at most: after that, pick sensible defaults for what's still open and write down which.
|
|
61
|
-
4. **Plan it.** Once the answers settle it, check in on the peloton (step 4 above) with the spec, `AGENTS.md`, and the prompt, then shape the idea as "Shaping an idea" above says, with three additions, all in one pull request in this repository: `AGENTS.md` says how to build, test, and check the chosen stack; the repository's agent prompt replaces its default sections (**Building**, **Checks**, **Pull requests**, **Direction**, and the rest) with what this project needs; and the spec opens with **In short**, a few plain sentences someone who isn't technical can check against what they asked for, naming the stack Pick for me chose or the one you recommended. The pull request's description starts with the same sentences under an `## In short` heading
|
|
61
|
+
4. **Plan it.** Once the answers settle it, check in on the peloton (step 4 above) with the spec, `AGENTS.md`, and the prompt, then shape the idea as "Shaping an idea" above says, with three additions, all in one pull request in this repository: `AGENTS.md` says how to build, test, and check the chosen stack; the repository's agent prompt replaces its default sections (**Building**, **Checks**, **Pull requests**, **Direction**, and the rest) with what this project needs; and the spec opens with **In short**, a few plain sentences someone who isn't technical can check against what they asked for, naming the stack Pick for me chose or the one you recommended. The pull request's description starts with the same sentences under an `## In short` heading (the kickoff's page on the board quotes them), followed by the spec's **How to check it**, which says how the owner will see the first version is what they asked for. The first task's done when is one the owner can check in a few minutes. Every task you add depends on the IDEA and carries one feature named for the first version, `<the repository's slug>-v1` (add it with `features add`, without a release); the first task sets up the stack, so building stays tasks. The pull request closes the IDEA; `modify <the task> --pr <number>` and watch it as in step 8.
|
|
62
62
|
|
|
63
63
|
Never start an agent, a chase, or a deploy for the project, and never set `--autostart`: the owner merges the plan and starts the building from the board.
|
|
64
64
|
|
package/scripts/tasks.mjs
CHANGED
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
textFields,
|
|
40
40
|
} from './tasks/structure.js';
|
|
41
41
|
import { looksLikeSecret } from '../src/ping.js';
|
|
42
|
+
import { sessionReport, shortHash, stubText } from '../src/session-report.js';
|
|
42
43
|
import { promptPathOf } from '../src/repos.js';
|
|
43
44
|
import { hookFailure, sessionProxy, routeThroughSessionProxy } from './tasks/proxy.js';
|
|
44
45
|
import { githubFromRemote, inRepo, pickRepo } from './tasks/repo.js';
|
|
@@ -623,6 +624,18 @@ async function startSessionLog(t) {
|
|
|
623
624
|
);
|
|
624
625
|
}
|
|
625
626
|
|
|
627
|
+
/**
|
|
628
|
+
* In a cloud session, what its claim tells the board about its environment, so the routine that started it reads
|
|
629
|
+
* Verified on Connections (BRK-142): whether a name was set, where the token came from, and the hash of this
|
|
630
|
+
* checkout's copy of the stub. Never a value. Null outside a cloud session.
|
|
631
|
+
*/
|
|
632
|
+
async function environmentReport() {
|
|
633
|
+
if (process.env.CLAUDE_CODE_REMOTE !== 'true') return null;
|
|
634
|
+
const path = ['tools/tasks/prompts/stub.md', 'prompts/stub.md'].map((p) => join(REPO, p)).find((p) => existsSync(p));
|
|
635
|
+
const stub = path ? await shortHash(stubText(readFileSync(path, 'utf8'))) : null;
|
|
636
|
+
return sessionReport({ env: process.env, file: fileEnv, named: Boolean(opts.as), stub });
|
|
637
|
+
}
|
|
638
|
+
|
|
626
639
|
/** The session hook couldn't post this checkout's live output: say so where the agent and the owner see it (BRK-86). */
|
|
627
640
|
function warnHookFailure() {
|
|
628
641
|
try {
|
|
@@ -1250,10 +1263,12 @@ const commands = {
|
|
|
1250
1263
|
async claim() {
|
|
1251
1264
|
// The board refuses a task of another repository than the one sent (--all doesn't widen a claim).
|
|
1252
1265
|
const { slug: repo } = await checkoutRepo();
|
|
1266
|
+
const session = await environmentReport();
|
|
1253
1267
|
const { task } = await call('POST', `tasks/${enc(need(args[0], 'task'))}/claim`, {
|
|
1254
1268
|
agent: agent(),
|
|
1255
1269
|
force: Boolean(opts.force),
|
|
1256
1270
|
...(repo ? { repo } : {}),
|
|
1271
|
+
...(session ? { session } : {}),
|
|
1257
1272
|
});
|
|
1258
1273
|
markSession(task);
|
|
1259
1274
|
await startSessionLog(task);
|
|
@@ -1539,6 +1554,8 @@ const commands = {
|
|
|
1539
1554
|
/** Every connection with its state and, for each that isn't working, the fix (IDEA-14). Read only. */
|
|
1540
1555
|
async connections() {
|
|
1541
1556
|
const STATE = { working: 'Working', attention: 'Needs attention', off: 'Not connected' };
|
|
1557
|
+
// A routine's row says whether a session it started has reported back (BRK-142).
|
|
1558
|
+
const READING = { verified: 'Verified', unverified: 'Not verified yet' };
|
|
1542
1559
|
const GROUP = {
|
|
1543
1560
|
repos: 'Repositories',
|
|
1544
1561
|
cloudflare: 'Cloudflare',
|
|
@@ -1568,7 +1585,7 @@ const commands = {
|
|
|
1568
1585
|
out.push('', GROUP[group] ?? group);
|
|
1569
1586
|
}
|
|
1570
1587
|
const when = c.at ? ` (${c.at.slice(0, 16).replace('T', ' ')})` : '';
|
|
1571
|
-
out.push(` ${STATE[c.state].padEnd(16)} ${c.name}: ${c.detail}${when}`);
|
|
1588
|
+
out.push(` ${(READING[c.reading] ?? STATE[c.state] ?? c.state).padEnd(16)} ${c.name}: ${c.detail}${when}`);
|
|
1572
1589
|
if (c.fix) out.push(` ${''.padEnd(16)} Fix: ${c.fix}`);
|
|
1573
1590
|
if (c.fix && c.link) out.push(` ${''.padEnd(16)} ${c.link}`);
|
|
1574
1591
|
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A cloud session's report on its own environment (BRK-142, docs/specs/IDEA-33-onboarding-hardening.md): the CLI
|
|
3
|
+
* sends it with a board-started agent's claim, and the routine that started the session reads Verified on
|
|
4
|
+
* Connections. Only yes/no facts and a hash of a public file are sent, never a value: whether a name was set,
|
|
5
|
+
* where the token came from, and the sha-256 of the checkout's copy of the stub. Pure, so it's tested without a
|
|
6
|
+
* Worker; the CLI builds a report with `sessionReport`, the board checks it with `checkReport`
|
|
7
|
+
* and `judgeStub` when it arrives, and reads it back with `reportProblems`.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Where the session's token came from: the cloud environment's API credential, a variable, or a tasks.env file. */
|
|
11
|
+
export const TOKEN_SOURCES = Object.freeze(['credential', 'variable', 'file']);
|
|
12
|
+
|
|
13
|
+
/** The stub's text the way it's hashed on both sides: line endings and trailing space don't count. */
|
|
14
|
+
export const stubText = (text) => String(text).replace(/\r\n/gu, '\n').trim();
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The report a cloud session's CLI sends with its claim, or null outside a cloud session. `env` is the environment,
|
|
18
|
+
* `file` the tasks.env settings, `named` whether --as named the agent, and `stub` the hash of the checkout's
|
|
19
|
+
* stub (null when the checkout has none).
|
|
20
|
+
* @param {{ env: Record<string, string | undefined>, file?: Record<string, string>, named?: boolean, stub?: string | null }} input
|
|
21
|
+
*/
|
|
22
|
+
export function sessionReport({ env, file = {}, named = false, stub = null }) {
|
|
23
|
+
if (env.CLAUDE_CODE_REMOTE !== 'true') return null;
|
|
24
|
+
const token = env.BREAKAWAY_TOKEN ? 'variable' : file.BREAKAWAY_TOKEN ? 'file' : 'credential';
|
|
25
|
+
return { token, agent: Boolean(named || env.BREAKAWAY_AGENT || file.BREAKAWAY_AGENT), stub: stub ?? null };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The report as the board keeps it, or null when it isn't one: only the known facts, so nothing else a client
|
|
30
|
+
* sends is ever stored.
|
|
31
|
+
* @returns {{ token: string, agent: boolean, stub: string | null } | null}
|
|
32
|
+
*/
|
|
33
|
+
export function checkReport(input) {
|
|
34
|
+
if (!input || typeof input !== 'object' || Array.isArray(input)) return null;
|
|
35
|
+
if (!TOKEN_SOURCES.includes(input.token) || typeof input.agent !== 'boolean') return null;
|
|
36
|
+
const stub = typeof input.stub === 'string' && /^[0-9a-f]{16}$/u.test(input.stub) ? input.stub : null;
|
|
37
|
+
return { token: input.token, agent: input.agent, stub };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The checkout's stub against the board's, both hashes: `same`, `different`, or `missing` (the checkout has none),
|
|
42
|
+
* and null when the board has no copy to compare with, so the stub isn't judged.
|
|
43
|
+
* @returns {'same' | 'different' | 'missing' | null}
|
|
44
|
+
*/
|
|
45
|
+
export function judgeStub(stub, boardStub) {
|
|
46
|
+
if (!boardStub) return null;
|
|
47
|
+
if (!stub) return 'missing';
|
|
48
|
+
return stub === boardStub ? 'same' : 'different';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* What's wrong in a kept report, each with its fix: no agent name, the token in a variable or a file instead of
|
|
53
|
+
* the API credential, and a stub that's missing from the checkout or differs from the board's (`stub`, judgeStub's
|
|
54
|
+
* verdict). `slug` names the repository and `host` the board in the fixes.
|
|
55
|
+
* @param {{ token: string, agent: boolean, stub: string | null }} report
|
|
56
|
+
* @param {{ slug: string, host: string }} board
|
|
57
|
+
* @returns {{ what: string, fix: string }[]}
|
|
58
|
+
*/
|
|
59
|
+
export function reportProblems(report, { slug, host }) {
|
|
60
|
+
const out = [];
|
|
61
|
+
if (!report.agent)
|
|
62
|
+
out.push({
|
|
63
|
+
what: 'it had no agent name (BREAKAWAY_AGENT)',
|
|
64
|
+
fix: 'Make the routine’s instructions the stub from the Agents view (Copy stub): it sends each agent to the prompt that sets BREAKAWAY_AGENT to the name the board gave it.',
|
|
65
|
+
});
|
|
66
|
+
if (report.token !== 'credential')
|
|
67
|
+
out.push({
|
|
68
|
+
what:
|
|
69
|
+
report.token === 'variable'
|
|
70
|
+
? 'it read the board’s token from BREAKAWAY_TOKEN, a variable anyone using the environment can see'
|
|
71
|
+
: 'it read the board’s token from a tasks.env file',
|
|
72
|
+
fix: `In the routine’s cloud environment on claude.ai, add the token as an API credential for ${host} and remove BREAKAWAY_TOKEN: the proxy then adds it, and the token never enters the session.`,
|
|
73
|
+
});
|
|
74
|
+
if (report.stub === 'different' || report.stub === 'missing')
|
|
75
|
+
out.push({
|
|
76
|
+
what:
|
|
77
|
+
report.stub === 'different'
|
|
78
|
+
? 'its checkout’s copy of the stub differs from the board’s'
|
|
79
|
+
: 'its checkout has no copy of the stub',
|
|
80
|
+
fix: `Bring ${slug}’s copy of the board’s files up to date (npx breakaway repos init ${slug} --update opens a pull request; in the board’s own repository, update the board to the same release), then paste the stub from the Agents view (Copy stub) as the routine’s instructions.`,
|
|
81
|
+
});
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** The first 16 hex digits of the sha-256 of `text`, with Web Crypto (the Worker and Node alike). */
|
|
86
|
+
export async function shortHash(text) {
|
|
87
|
+
const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(text));
|
|
88
|
+
return [...new Uint8Array(digest)]
|
|
89
|
+
.map((b) => b.toString(16).padStart(2, '0'))
|
|
90
|
+
.join('')
|
|
91
|
+
.slice(0, 16);
|
|
92
|
+
}
|