breakaway 1.4.0-main.28 → 1.4.0-main.3
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/.agents/skills/tasks/SKILL.md +0 -1
- package/package.json +1 -4
- package/prompts/core.md +0 -1
- package/scripts/tasks/cli.js +9 -141
- package/scripts/tasks/init.js +562 -3
- package/scripts/tasks.mjs +9 -146
- package/src/cli-version.js +2 -2
- package/src/prompt.js +1 -1
- package/src/repos.js +9 -28
- package/scripts/board-files.mjs +0 -18
- package/scripts/deploy-plan.mjs +0 -135
- package/scripts/lib/deploy-plan.js +0 -102
- package/scripts/lib/package-release.js +0 -90
- package/scripts/package-release.mjs +0 -60
- package/scripts/tasks/pipeline.js +0 -911
- package/src/init.js +0 -590
- package/src/packages.js +0 -56
- package/src/specs.js +0 -96
- package/template/pipeline/ci.yml +0 -40
- package/template/pipeline/deploy.yml +0 -178
- package/template/pipeline/promote.yml +0 -203
- package/template/pipeline/release.yml +0 -283
- package/template/pipeline/rollback.yml +0 -98
|
@@ -31,7 +31,6 @@ breakaway's work is on the board that tracks this repository. The CLI is `npx br
|
|
|
31
31
|
| You need the owner to choose | Ask with a decision, not prose: `add "<title>" --tag owner --decision <file.json>` and make the work that waits `--depends` on it. Only the owner answers, on the board; read the answers with `show`. |
|
|
32
32
|
| Part of the work needs the owner (an install, a dashboard, a sign-off) | Finish your part, then `add` a `+owner` task for the rest that `--depends` on yours. |
|
|
33
33
|
| Task needs design choices | Write the spec in `docs/specs/<ID>-<slug>.md` and `modify <ID> --spec <path>`. |
|
|
34
|
-
| Reading the repository's specs | `tasks specs` lists them, newest first, with each one's status and its tasks; `specs show <path>` prints one with the tasks that link it. They're read from GitHub's default branch, so a spec still in a pull request isn't there yet. |
|
|
35
34
|
| You're blocked by another task | `comment` why, `release`, and pick the blocker or another task. |
|
|
36
35
|
| A claim looks abandoned | Ask the owner; don't take it. |
|
|
37
36
|
| Opening a pull request for a spec, plan, or partial step | Write `Part of <ID>.`, not `Closes`, and don't put it in `--pr`: merging the pull request in that field finishes the task. A branch name alone never closes anything. |
|
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.3",
|
|
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",
|
|
@@ -21,16 +21,13 @@
|
|
|
21
21
|
"!scripts/install/*.test.js",
|
|
22
22
|
"src/cli-version.js",
|
|
23
23
|
"src/decision.js",
|
|
24
|
-
"src/init.js",
|
|
25
24
|
"src/install.js",
|
|
26
25
|
"src/model.js",
|
|
27
|
-
"src/packages.js",
|
|
28
26
|
"src/ping.js",
|
|
29
27
|
"src/promote.js",
|
|
30
28
|
"src/prompt.js",
|
|
31
29
|
"src/redact.js",
|
|
32
30
|
"src/repos.js",
|
|
33
|
-
"src/specs.js",
|
|
34
31
|
"src/versions.js",
|
|
35
32
|
"prompts/*.md",
|
|
36
33
|
"taskrc",
|
package/prompts/core.md
CHANGED
|
@@ -104,7 +104,6 @@ The owner wrote what they want in their own words and pressed Start; the board m
|
|
|
104
104
|
A general task released with no pull request is finished: the board closes it, so release only when your part is done.
|
|
105
105
|
4. **Other tasks: change them directly, each change noted.** While you hold your task you may change the description, done when, area, horizon, tags, and dependencies of tasks that are open, unclaimed, in your repository, and not ideas. The board adds `Changed by <your task>: <the fields>.` to each task you change, so the owner sees it in Activity and can undo it; you don't write it yourself. Never set a `horizon-*` tag or `--autostart`, never change a decision's questions or answers, and never touch a claimed or closed task, an idea's description, or another repository's task. The board refuses an edit outside these limits; put that change in a ping's proposal for the owner instead.
|
|
106
106
|
5. **A run from a decision's answers.** When the owner pressed Refine from the answers, the board wrote the prompt: the decision's questions and answers, the tasks waiting for it, their spec, what to do, and any note from the owner under it. Your task is related to the decision; `show` it for the full answers. Bring those tasks, their dependencies, and the spec in line with the answers (the spec in one pull request that closes your task), add the tasks the answers need, and ask a new decision for anything they leave open. Never change the answers: only the owner does.
|
|
107
|
-
6. **A run that names a spec.** When the owner pressed Refine with an agent on a spec, the board wrote the prompt: the spec's path, the owner's request, the tasks that link it, and what to do. Your task's `spec` is that path. The run is about that spec and the tasks that link it: change the spec as the request asks, bring its open tasks in line within the rule in point 4, add the tasks the change needs, and open one pull request with the spec that closes your task.
|
|
108
107
|
|
|
109
108
|
You never start or force-start an agent, never take another agent's claim, and never deploy, touch production, or merge, whatever the prompt says.
|
|
110
109
|
|
package/scripts/tasks/cli.js
CHANGED
|
@@ -14,7 +14,6 @@ export const SUBCOMMANDS = {
|
|
|
14
14
|
horizon: ['close'],
|
|
15
15
|
hook: ['session', 'wait'],
|
|
16
16
|
peloton: ['checkin', 'step', 'reply'],
|
|
17
|
-
specs: ['list', 'show'],
|
|
18
17
|
};
|
|
19
18
|
|
|
20
19
|
/** Commands that take nothing after their name, so a word there is a mistake (an old copy's missing subcommand, say). */
|
|
@@ -105,30 +104,6 @@ export function pullAgentRequest(action, number, { repo = null, problem, note, f
|
|
|
105
104
|
return { request: ['POST', `github/pulls/${n}/${action}`, body] };
|
|
106
105
|
}
|
|
107
106
|
|
|
108
|
-
/**
|
|
109
|
-
* `npx breakaway github release <pre-release> [--next patch|minor|major]` (BRK-103, WEB-39): the owner releases a
|
|
110
|
-
* package's pre-release as its stable, as Release on the GitHub page does, with what the default branch works toward
|
|
111
|
-
* next. The board starts the repository's release.yml stable job, and npm waits for the owner's 2FA; it refuses an
|
|
112
|
-
* agent, so the request always says who asks, and refuses a pre-release whose stable is already out (409).
|
|
113
|
-
* @param {string | undefined} version the pre-release, like 1.4.0-main.5 (or its tag)
|
|
114
|
-
* @param {{ repo?: string | null, by?: string, next?: string | null }} [options]
|
|
115
|
-
* @returns {{ error?: string, request?: [string, string, Record<string, string>] }}
|
|
116
|
-
*/
|
|
117
|
-
export function packageReleaseRequest(version, { repo = null, by, next = null } = {}) {
|
|
118
|
-
const v = String(version ?? '').trim();
|
|
119
|
-
if (!/^(?:\S+@|v)?\d+\.\d+\.\d+-main\.\d+$/u.test(v))
|
|
120
|
-
return { error: 'say which pre-release: npx breakaway github release <version>, like 1.4.0-main.5' };
|
|
121
|
-
if (next !== null && next !== undefined && !['patch', 'minor', 'major'].includes(String(next)))
|
|
122
|
-
return { error: '--next is patch, minor, or major' };
|
|
123
|
-
return {
|
|
124
|
-
request: [
|
|
125
|
-
'POST',
|
|
126
|
-
'github/release',
|
|
127
|
-
{ version: v, ...(next ? { next: String(next) } : {}), ...(repo ? { repo } : {}), ...(by ? { by } : {}) },
|
|
128
|
-
],
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
107
|
export const REVIEW_VERDICTS = ['ready', 'follow-up', 'changes'];
|
|
133
108
|
|
|
134
109
|
/**
|
|
@@ -173,27 +148,17 @@ export function forceFields(force, by) {
|
|
|
173
148
|
* With `decision` (`agents new --decision <ID> ["<note>"]`, BRK-110) the board writes the prompt from that answered
|
|
174
149
|
* decision, in the decision's repository, and the text is the owner's note under it. With `next`
|
|
175
150
|
* (`agents new --next minor|major ["<note>"]`, BRK-100) it writes the prompt that sets the repository's next version.
|
|
176
|
-
* With `spec` (`agents new --spec <path> "<what should change>"`, BRK-121) it writes the prompt that refines that spec
|
|
177
|
-
* and the tasks that link it, and the text, required, is what should change.
|
|
178
151
|
* @param {string} prompt
|
|
179
|
-
* @param {{ repo?: string | null, force?: boolean, by?: string, decision?: string | null, next?: string | null
|
|
152
|
+
* @param {{ repo?: string | null, force?: boolean, by?: string, decision?: string | null, next?: string | null }} [options]
|
|
180
153
|
*/
|
|
181
|
-
export function generalAgentRequest(
|
|
182
|
-
prompt,
|
|
183
|
-
{ repo = null, force = false, by, decision = null, next = null, spec = null } = {},
|
|
184
|
-
) {
|
|
154
|
+
export function generalAgentRequest(prompt, { repo = null, force = false, by, decision = null, next = null } = {}) {
|
|
185
155
|
const text = String(prompt ?? '').trim();
|
|
186
|
-
if (
|
|
187
|
-
return { error: 'start one from --decision, --spec, or --next: only one of them' };
|
|
156
|
+
if (decision && next) return { error: 'start one from --decision or --next, not both' };
|
|
188
157
|
if (next && !['minor', 'major'].includes(next))
|
|
189
158
|
return { error: 'patches count by themselves: --next minor or --next major' };
|
|
190
|
-
if (spec && !text)
|
|
191
|
-
return {
|
|
192
|
-
error: 'say what should change in the spec: npx breakaway agents new --spec <path> "<what should change>"',
|
|
193
|
-
};
|
|
194
159
|
if (!text && !decision && !next)
|
|
195
160
|
return { error: 'say what the agent should do: npx breakaway agents new "Tidy the docs" [--image <file>]' };
|
|
196
|
-
const board = decision ? { decision } : next ? { next } :
|
|
161
|
+
const board = decision ? { decision } : next ? { next } : null;
|
|
197
162
|
const body = {
|
|
198
163
|
...(board ? { ...board, ...(text ? { note: text } : {}) } : { prompt: text }),
|
|
199
164
|
...(repo ? { repo } : {}),
|
|
@@ -205,54 +170,18 @@ export function generalAgentRequest(
|
|
|
205
170
|
|
|
206
171
|
/**
|
|
207
172
|
* What the CLI says about a general agent's answer: the task and that it started, or why it waits (and whether Force
|
|
208
|
-
* start could skip that), or, from a decision
|
|
209
|
-
* that already has it.
|
|
173
|
+
* start could skip that), or, from a decision or for the next version (`next`), the open one that already has it.
|
|
210
174
|
* @param {{ task: { wid?: string, short?: string }, run?: { url?: string, agent?: string } | null, waiting?: string | null, forceable?: boolean, already?: string | null }} answer
|
|
211
|
-
* @param {{ next?: string | null
|
|
175
|
+
* @param {{ next?: string | null }} [options]
|
|
212
176
|
*/
|
|
213
|
-
export function generalAgentSummary({ task, run, waiting, forceable, already }, { next = null
|
|
177
|
+
export function generalAgentSummary({ task, run, waiting, forceable, already }, { next = null } = {}) {
|
|
214
178
|
const id = task.wid ?? task.short;
|
|
215
|
-
if (!run && already)
|
|
216
|
-
|
|
217
|
-
return `${id} already ${what}: ${already}.`;
|
|
218
|
-
}
|
|
179
|
+
if (!run && already)
|
|
180
|
+
return `${id} already ${next ? 'prepares the next version' : 'refines from these answers'}: ${already}.`;
|
|
219
181
|
if (run) return `Started ${run.agent ? `${run.agent} ` : 'an agent '}on ${id}${run.url ? `: ${run.url}` : ''}`;
|
|
220
182
|
return `Saved ${id}, waiting to start: ${waiting ?? 'no room yet'}.${forceable ? ` Start it now past the board's limits: npx breakaway agents start ${id} --force` : ''}`;
|
|
221
183
|
}
|
|
222
184
|
|
|
223
|
-
/** A spec's path as the board reads it: no leading `./`, no doubled or trailing slashes. */
|
|
224
|
-
const specPath = (path) =>
|
|
225
|
-
String(path ?? '')
|
|
226
|
-
.trim()
|
|
227
|
-
.replace(/^(\.\/)+/u, '')
|
|
228
|
-
.split('/')
|
|
229
|
-
.filter((part) => part && part !== '.')
|
|
230
|
-
.join('/');
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* The request behind `npx breakaway specs [list]` (BRK-121): the specs of the checkout's repository, or the one `--repo`
|
|
234
|
-
* names; without either, the board answers with its default repository's.
|
|
235
|
-
* @param {string | null} repo
|
|
236
|
-
* @returns {[string, string, undefined]}
|
|
237
|
-
*/
|
|
238
|
-
export function specsRequest(repo) {
|
|
239
|
-
return ['GET', repo ? `specs?repo=${encodeURIComponent(repo)}` : 'specs', undefined];
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* The request behind `npx breakaway specs show <path>` (BRK-121): one spec, by its path in the repository. The board
|
|
244
|
-
* refuses a path outside the specs directory; one that climbs out with `..` is refused here first.
|
|
245
|
-
* @param {string | undefined} path
|
|
246
|
-
* @param {string | null} repo
|
|
247
|
-
*/
|
|
248
|
-
export function specRequest(path, repo) {
|
|
249
|
-
const clean = specPath(path);
|
|
250
|
-
if (!clean) return { error: 'say which spec: npx breakaway specs show <path>, like docs/specs/BRK-1-thing.md' };
|
|
251
|
-
if (clean.split('/').includes('..')) return { error: `${clean.slice(0, 200)} climbs out of the repository` };
|
|
252
|
-
const query = repo ? `?repo=${encodeURIComponent(repo)}` : '';
|
|
253
|
-
return { request: ['GET', `specs/${clean.split('/').map(encodeURIComponent).join('/')}${query}`, undefined] };
|
|
254
|
-
}
|
|
255
|
-
|
|
256
185
|
/**
|
|
257
186
|
* What the CLI says about an answer to those requests: which task and agent took the pull request, or who already has it.
|
|
258
187
|
* @param {'fix' | 'review'} action
|
|
@@ -480,64 +409,3 @@ export function chaseSummary(slug, { dryRun, chase, started = [], wouldStart = [
|
|
|
480
409
|
}
|
|
481
410
|
return [first, '', ...chaseLines(chase, slug)].join('\n');
|
|
482
411
|
}
|
|
483
|
-
|
|
484
|
-
/** A spec's tasks in a few words: "3 tasks, 2 open", or "no tasks". */
|
|
485
|
-
const specTaskCount = (tasks = []) => {
|
|
486
|
-
if (!tasks.length) return 'no tasks';
|
|
487
|
-
const open = tasks.filter((t) => t.status === 'pending').length;
|
|
488
|
-
return `${plural(tasks.length, 'task')}, ${open} open`;
|
|
489
|
-
};
|
|
490
|
-
|
|
491
|
-
/**
|
|
492
|
-
* What `npx breakaway specs` prints: the repository's specs newest first, each with its work ID, status, title, and its
|
|
493
|
-
* tasks' count; with none, where specs go and how to point the board at another directory.
|
|
494
|
-
* @param {{ slug: string, dir: string, missing?: boolean, readme?: { path: string } | null, specs: any[] }} answer
|
|
495
|
-
*/
|
|
496
|
-
export function specListLines({ slug, dir, missing, readme, specs }) {
|
|
497
|
-
if (!specs.length)
|
|
498
|
-
return [
|
|
499
|
-
`No specs in ${dir} yet${missing ? `: ${slug} has no ${dir} on its default branch` : ''}.`,
|
|
500
|
-
'A spec is a Markdown file in that directory, merged like any change.',
|
|
501
|
-
`If ${slug} keeps its specs somewhere else, the owner sets it with npx breakaway repos modify ${slug} --specs <dir>.`,
|
|
502
|
-
];
|
|
503
|
-
const intro = readme ? ` (its introduction is ${readme.path})` : '';
|
|
504
|
-
const out = [`${slug}: ${plural(specs.length, 'spec')} in ${dir}${intro}`, ''];
|
|
505
|
-
for (const s of specs) {
|
|
506
|
-
const extra = s.tooLarge ? ', over 1 MB: read it on GitHub' : '';
|
|
507
|
-
out.push(
|
|
508
|
-
` ${(s.wid ?? '').padEnd(9)} ${(s.status ?? '-').padEnd(10)} ${s.title} (${specTaskCount(s.tasks)}${extra})`,
|
|
509
|
-
);
|
|
510
|
-
}
|
|
511
|
-
out.push('', `Read one: npx breakaway specs show <path>, like ${specs[0].path}`);
|
|
512
|
-
return out;
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
/**
|
|
516
|
-
* What `npx breakaway specs show <path>` prints: the spec's title and path, status, the commit that last changed it,
|
|
517
|
-
* its GitHub link, its Markdown (or, over 1 MB, a pointer to GitHub), and the tasks that link it.
|
|
518
|
-
* @param {any} spec
|
|
519
|
-
*/
|
|
520
|
-
export function specLines(spec) {
|
|
521
|
-
const out = [`${spec.title} (${spec.path})`, ''];
|
|
522
|
-
const row = (k, v) => v && out.push(` ${k.padEnd(11)} ${v}`);
|
|
523
|
-
row('Status', spec.status);
|
|
524
|
-
const c = spec.commit;
|
|
525
|
-
if (c)
|
|
526
|
-
row(
|
|
527
|
-
'Changed',
|
|
528
|
-
`${c.date ? `${String(c.date).slice(0, 16).replace('T', ' ')} ` : ''}in ${String(c.sha).slice(0, 7)}${c.message ? `: ${c.message}` : ''}`,
|
|
529
|
-
);
|
|
530
|
-
row('GitHub', spec.url);
|
|
531
|
-
out.push('');
|
|
532
|
-
if (spec.tooLarge || spec.text === null || spec.text === undefined)
|
|
533
|
-
out.push('Over 1 MB, too large to show here: read it on GitHub.');
|
|
534
|
-
else out.push(String(spec.text).replace(/\s+$/u, ''));
|
|
535
|
-
const tasks = spec.tasks ?? [];
|
|
536
|
-
out.push('');
|
|
537
|
-
if (tasks.length) {
|
|
538
|
-
out.push(` Tasks (${tasks.length}, ${tasks.filter((t) => t.status === 'pending').length} open)`);
|
|
539
|
-
for (const t of tasks) out.push(` ${idOf(t).padEnd(9)} ${t.status.padEnd(9)} ${t.description}`);
|
|
540
|
-
} else out.push(` No task links it yet: npx breakaway modify <ref> --spec ${spec.path}`);
|
|
541
|
-
out.push('', `Refine it: npx breakaway agents new --spec ${spec.path} "<what should change>"`);
|
|
542
|
-
return out;
|
|
543
|
-
}
|