alignfirst 0.1.0-beta.2 → 0.1.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -4
- package/dist/cli.js +1 -1
- package/dist/commands/guide.js +14 -8
- package/dist/commands/ticket.js +88 -24
- package/dist/conventions.js +2 -2
- package/dist/format.d.ts +2 -0
- package/dist/format.js +23 -0
- package/dist/plans/catchup.d.ts +2 -0
- package/dist/plans/catchup.js +55 -0
- package/dist/plans/ticket.d.ts +13 -3
- package/dist/plans/ticket.js +24 -9
- package/dist/protocols.d.ts +1 -1
- package/dist/protocols.js +1 -9
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +2 -0
- package/package.json +1 -1
- package/templates/guide/core.md +13 -40
- package/templates/guide/overview.md +9 -7
- package/templates/guide/protocols/aad.md +2 -9
- package/templates/guide/protocols/description.md +4 -9
- package/templates/guide/protocols/merge.md +2 -7
- package/templates/guide/protocols/plan.md +12 -14
- package/templates/guide/protocols/review.md +4 -5
- package/templates/guide/protocols/spec.md +2 -7
- package/templates/guide/selection.md +19 -0
- package/templates/guide/protocols/catchup.md +0 -13
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ The guide installs the selected components and configures the repository. Remove
|
|
|
35
35
|
## Commands
|
|
36
36
|
|
|
37
37
|
- `guide` — Print an AlignFirst protocol.
|
|
38
|
-
- `ticket` — Resolve a ticket directory
|
|
38
|
+
- `ticket` — Resolve a ticket directory, load its history, or get its next file.
|
|
39
39
|
- `sync` — Synchronize shared plans.
|
|
40
40
|
- `plans` — Set up, check and archive plans.
|
|
41
41
|
- `docmap` — Browse project documentation.
|
|
@@ -44,18 +44,19 @@ The guide installs the selected components and configures the repository. Remove
|
|
|
44
44
|
- `config` — Report the effective project configuration.
|
|
45
45
|
- `doctor` — Diagnose an AlignFirst setup.
|
|
46
46
|
|
|
47
|
-
Run `alignfirst --help` for command usage or `alignfirst guide`
|
|
47
|
+
Run `alignfirst --help` for command usage or `alignfirst guide` to choose a protocol. `alignfirst guide <protocol>` prints the selected protocol followed by shared conventions. Add `--protocol-only` when those conventions are already in context.
|
|
48
48
|
|
|
49
49
|
## Agent skills
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
Ten optional Agent Skill stubs expose the CLI to GitHub Copilot, Cursor, Claude Code, and Codex. Protocol skills reuse guides already in context and load missing guides through `npx -y alignfirst guide`. Catchup skills load ticket history through `npx -y alignfirst ticket --catchup`.
|
|
52
52
|
|
|
53
53
|
Install them globally:
|
|
54
54
|
|
|
55
55
|
```sh
|
|
56
56
|
npx skills add https://github.com/paleo/alignfirst --global \
|
|
57
57
|
--skill alignfirst --skill al --skill alplan --skill alspec \
|
|
58
|
-
--skill aldescription --skill alreview --skill alcatchup --skill almerge
|
|
58
|
+
--skill aldescription --skill alreview --skill alcatchup --skill almerge \
|
|
59
|
+
--skill alcatchupaad --skill alcatchupspec
|
|
59
60
|
```
|
|
60
61
|
|
|
61
62
|
Restart the agent after installation. Claude Code, GitHub Copilot, and Cursor expose skills with `/`; Codex uses `$`.
|
|
@@ -73,11 +74,17 @@ These examples use the `/` form. Replace it with `$` in Codex.
|
|
|
73
74
|
| Review | `/alreview` | Review the current branch against its base. |
|
|
74
75
|
| Merge | `/almerge` | Resolve merge or rebase conflicts. |
|
|
75
76
|
| Catch up | `/alcatchup` | Load the current task history and continue. |
|
|
77
|
+
| Catch up and align | `/alcatchupaad <request>` | Load history, then start AAD. |
|
|
78
|
+
| Catch up and specify | `/alcatchupspec <request>` | Load history, then start specification. |
|
|
76
79
|
|
|
77
80
|
To implement a plan, start a fresh agent context and ask it to execute the plan file.
|
|
78
81
|
|
|
79
82
|
AlignFirst stores specifications, plans, and summaries in `.plans/<ticket-id>/`. It normally derives the ticket ID from the request or branch and asks when none is available. Files use a cycle letter and sequence number, such as `A1-spec.md` and `A2-plan.md`.
|
|
80
83
|
|
|
84
|
+
## Catchup output
|
|
85
|
+
|
|
86
|
+
`alignfirst ticket [<id>] --catchup` prints the ticket's Markdown files, plans excluded, each in a `<file>` block carrying its path and modification time. When the history exceeds the output budget, only the entry list is printed so the agent can read the relevant files.
|
|
87
|
+
|
|
81
88
|
## Updates
|
|
82
89
|
|
|
83
90
|
```sh
|
package/dist/cli.js
CHANGED
|
@@ -59,7 +59,7 @@ function renderHelp(ctx) {
|
|
|
59
59
|
|
|
60
60
|
Usage:
|
|
61
61
|
${ctx.form} guide [<protocol>]
|
|
62
|
-
${ctx.form} ticket [<id>]
|
|
62
|
+
${ctx.form} ticket [<id>] [--catchup]
|
|
63
63
|
${ctx.form} sync [--auto-archive | --no-auto-archive]
|
|
64
64
|
${ctx.form} plans <command>
|
|
65
65
|
${ctx.form} docmap [<arguments>]
|
package/dist/commands/guide.js
CHANGED
|
@@ -11,7 +11,7 @@ import { resolvePlansMode } from "../plans/mode.js";
|
|
|
11
11
|
import { detectTicketFromBranch } from "../plans/ticket.js";
|
|
12
12
|
import { PROTOCOLS } from "../protocols.js";
|
|
13
13
|
const TICKET_CMD_PLACEHOLDER = "{{TICKET_CMD}}";
|
|
14
|
-
const
|
|
14
|
+
const TICKET_DETECTION_PLACEHOLDER = "{{TICKET_DETECTION}}";
|
|
15
15
|
const PLANS_STATE_PLACEHOLDER = "{{PLANS_STATE}}";
|
|
16
16
|
const COMMIT_RULE_PLACEHOLDER = "{{COMMIT_RULE}}";
|
|
17
17
|
const BASE_BRANCH_RULE_PLACEHOLDER = "{{BASE_BRANCH_RULE}}";
|
|
@@ -102,9 +102,15 @@ function renderGuide(ctx, options) {
|
|
|
102
102
|
return applyPlaceholders(readProtocolTemplate(options.protocol), placeholders);
|
|
103
103
|
const core = renderCoreGuide(ctx, placeholders);
|
|
104
104
|
if (options.protocol === undefined)
|
|
105
|
-
return core
|
|
105
|
+
return `${readGuideTemplate("selection.md")}\n\n${core}`;
|
|
106
106
|
const protocol = applyPlaceholders(readProtocolTemplate(options.protocol), placeholders);
|
|
107
|
-
|
|
107
|
+
const [title, ...sections] = protocol.split("\n\n");
|
|
108
|
+
return [
|
|
109
|
+
title,
|
|
110
|
+
"This guide includes the selected protocol and shared conventions. Read both before starting.",
|
|
111
|
+
...sections,
|
|
112
|
+
core,
|
|
113
|
+
].join("\n\n");
|
|
108
114
|
}
|
|
109
115
|
function renderReviewerGuide(perspective, modules) {
|
|
110
116
|
const templates = [
|
|
@@ -125,13 +131,13 @@ function buildGuidePlaceholders(ctx, protocol) {
|
|
|
125
131
|
const detection = pattern === undefined ? undefined : detectTicketFromBranch(ctx.cwd, pattern);
|
|
126
132
|
return {
|
|
127
133
|
ticketCommand: detection?.kind === "detected" ? "{{CMD}} ticket" : "{{CMD}} ticket <id>",
|
|
128
|
-
|
|
134
|
+
ticketDetection: renderTicketDetection(pattern, detection),
|
|
129
135
|
plansState: renderPlansState(ctx),
|
|
130
136
|
commitRule: renderCommitRule(ctx),
|
|
131
137
|
baseBranchRule: renderBaseBranchRule(ctx, protocol),
|
|
132
138
|
};
|
|
133
139
|
}
|
|
134
|
-
function
|
|
140
|
+
function renderTicketDetection(pattern, detection) {
|
|
135
141
|
if (pattern === undefined)
|
|
136
142
|
return "Ask the user for the ticket ID when it is not given.";
|
|
137
143
|
if (detection?.kind === "detected")
|
|
@@ -146,7 +152,7 @@ function renderPlansState(ctx) {
|
|
|
146
152
|
return `\`\`\`text\n${missingPlansMessage(ctx.form)}\n\`\`\``;
|
|
147
153
|
try {
|
|
148
154
|
return resolvePlansMode(ctx.cwd, ctx.form).kind === "shared"
|
|
149
|
-
? "After every change in
|
|
155
|
+
? "After every change in TICKET_DIR, run `{{CMD}} sync`."
|
|
150
156
|
: "";
|
|
151
157
|
}
|
|
152
158
|
catch {
|
|
@@ -156,7 +162,7 @@ function renderPlansState(ctx) {
|
|
|
156
162
|
function renderCommitRule(ctx) {
|
|
157
163
|
const commit = ctx.projectConfig?.config.git?.commit;
|
|
158
164
|
if (commit === undefined)
|
|
159
|
-
return "(follow the convention you are aware of, or default to `<type>: [
|
|
165
|
+
return "(follow the convention you are aware of, or default to `<type>: [TICKET_ID] very short description`)";
|
|
160
166
|
const { subject, side } = commitSubject(commit);
|
|
161
167
|
const rule = side === undefined ? subject : `${subject}; ${side}`;
|
|
162
168
|
return `(project convention: ${rule})`;
|
|
@@ -177,7 +183,7 @@ function applyPlaceholders(template, values) {
|
|
|
177
183
|
return template
|
|
178
184
|
.replaceAll(`${PLANS_STATE_PLACEHOLDER}\n\n`, () => values.plansState === "" ? "" : `${values.plansState}\n\n`)
|
|
179
185
|
.replaceAll(TICKET_CMD_PLACEHOLDER, () => values.ticketCommand)
|
|
180
|
-
.replaceAll(
|
|
186
|
+
.replaceAll(TICKET_DETECTION_PLACEHOLDER, () => values.ticketDetection)
|
|
181
187
|
.replaceAll(PLANS_STATE_PLACEHOLDER, () => values.plansState)
|
|
182
188
|
.replaceAll(COMMIT_RULE_PLACEHOLDER, () => values.commitRule)
|
|
183
189
|
.replaceAll(BASE_BRANCH_RULE_PLACEHOLDER, () => values.baseBranchRule);
|
package/dist/commands/ticket.js
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { join, relative } from "node:path";
|
|
2
2
|
import { parseArgs } from "node:util";
|
|
3
3
|
import { CliError } from "../cli-error.js";
|
|
4
|
+
import { formatLocalTimestamp, formatSize } from "../format.js";
|
|
4
5
|
import { parseCommandArgs } from "../parse-args.js";
|
|
6
|
+
import { renderCatchup } from "../plans/catchup.js";
|
|
5
7
|
import { assertPlansGate } from "../plans/layout.js";
|
|
6
|
-
import { deduceTicketFromBranch,
|
|
8
|
+
import { deduceTicketFromBranch, nextFilePosition, peekSideTicket, reserveSideTicket, resolveTicketDir, validateTicketId, } from "../plans/ticket.js";
|
|
7
9
|
const USAGE = `Usage:
|
|
8
|
-
{{FORM}} ticket [<id>] [--next <filename>] [--new-cycle] [--json] [--dry-run]
|
|
9
|
-
{{FORM}} ticket --side [--json] [--dry-run]
|
|
10
|
+
{{FORM}} ticket [<id>] [--next [<filename>]] [--new-cycle] [--json] [--dry-run]
|
|
11
|
+
{{FORM}} ticket --side [--next [<filename>]] [--new-cycle] [--json] [--dry-run]
|
|
12
|
+
{{FORM}} ticket [<id> | --side] --catchup
|
|
13
|
+
|
|
14
|
+
--catchup prints the ticket's Markdown files, plans excluded, summaries included.
|
|
15
|
+
Files over 64 KiB are listed without content. Above 30 KiB of output, only the entry
|
|
16
|
+
list is printed.
|
|
10
17
|
`;
|
|
11
18
|
export function runTicket(ctx, args) {
|
|
12
19
|
assertPlansGate(ctx.cwd, ctx.form);
|
|
@@ -15,27 +22,34 @@ export function runTicket(ctx, args) {
|
|
|
15
22
|
if (parsed === undefined)
|
|
16
23
|
return 0;
|
|
17
24
|
const result = resolveTicket(ctx, parsed);
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
25
|
+
if (parsed.catchup) {
|
|
26
|
+
ctx.stdout.write(renderCatchup(ctx.cwd, result, renderReport(ctx, parsed, result)));
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
29
|
+
if (parsed.next !== undefined) {
|
|
30
|
+
writeNextReport(ctx, parsed, result, parsed.next);
|
|
31
|
+
return 0;
|
|
32
|
+
}
|
|
21
33
|
if (parsed.json)
|
|
22
|
-
ctx.stdout.write(`${JSON.stringify(jsonReport(ctx, parsed, result
|
|
34
|
+
ctx.stdout.write(`${JSON.stringify(jsonReport(ctx, parsed, result), undefined, 2)}\n`);
|
|
23
35
|
else
|
|
24
|
-
ctx.stdout.write(renderReport(ctx, parsed, result
|
|
36
|
+
ctx.stdout.write(renderReport(ctx, parsed, result));
|
|
25
37
|
return 0;
|
|
26
38
|
}
|
|
27
39
|
function renderUsage(ctx) {
|
|
28
40
|
return USAGE.replaceAll("{{FORM}}", ctx.form);
|
|
29
41
|
}
|
|
30
42
|
function parseTicketArgs(ctx, args, usage) {
|
|
43
|
+
const normalized = normalizeNextArgs(args);
|
|
31
44
|
const { values, positionals } = parseCommandArgs(usage, () => parseArgs({
|
|
32
|
-
args,
|
|
45
|
+
args: normalized.args,
|
|
33
46
|
options: {
|
|
34
|
-
next: { type: "
|
|
47
|
+
next: { type: "boolean" },
|
|
35
48
|
"new-cycle": { type: "boolean", default: false },
|
|
36
49
|
json: { type: "boolean", default: false },
|
|
37
50
|
"dry-run": { type: "boolean", default: false },
|
|
38
51
|
side: { type: "boolean", default: false },
|
|
52
|
+
catchup: { type: "boolean", default: false },
|
|
39
53
|
help: { type: "boolean", short: "h", default: false },
|
|
40
54
|
},
|
|
41
55
|
strict: true,
|
|
@@ -51,18 +65,46 @@ function parseTicketArgs(ctx, args, usage) {
|
|
|
51
65
|
throw new CliError(`A ticket id cannot be combined with --side.\n\n${usage}`);
|
|
52
66
|
if (values["new-cycle"] && values.next === undefined)
|
|
53
67
|
throw new CliError(`--new-cycle requires --next.\n\n${usage}`);
|
|
54
|
-
if (values.next !== undefined)
|
|
55
|
-
|
|
68
|
+
if (values.catchup && (values.next !== undefined || values.json || values["dry-run"]))
|
|
69
|
+
throw new CliError(`--catchup cannot be combined with --next, --json, or --dry-run.\n\n${usage}`);
|
|
70
|
+
if (normalized.filename !== undefined)
|
|
71
|
+
validateNextFilename(normalized.filename);
|
|
56
72
|
const resolution = resolveTicketId(ctx, positionals[0], values.side, values["dry-run"]);
|
|
57
73
|
return {
|
|
58
74
|
...resolution,
|
|
59
|
-
next: values.next,
|
|
75
|
+
next: values.next === undefined ? undefined : (normalized.filename ?? true),
|
|
60
76
|
newCycle: values["new-cycle"],
|
|
61
77
|
json: values.json,
|
|
62
78
|
dryRun: values["dry-run"],
|
|
63
79
|
side: values.side,
|
|
80
|
+
catchup: values.catchup,
|
|
64
81
|
};
|
|
65
82
|
}
|
|
83
|
+
function normalizeNextArgs(args) {
|
|
84
|
+
const normalized = { args: [] };
|
|
85
|
+
for (let index = 0; index < args.length; ++index) {
|
|
86
|
+
const arg = args[index];
|
|
87
|
+
if (arg === "--") {
|
|
88
|
+
normalized.args.push(...args.slice(index));
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
if (arg.startsWith("--next=")) {
|
|
92
|
+
normalized.filename = arg.slice("--next=".length);
|
|
93
|
+
normalized.args.push("--next");
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
normalized.args.push(arg);
|
|
97
|
+
if (arg !== "--next")
|
|
98
|
+
continue;
|
|
99
|
+
const following = args[index + 1];
|
|
100
|
+
delete normalized.filename;
|
|
101
|
+
if (following !== undefined && !following.startsWith("-")) {
|
|
102
|
+
normalized.filename = following;
|
|
103
|
+
++index;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return normalized;
|
|
107
|
+
}
|
|
66
108
|
function validateNextFilename(filename) {
|
|
67
109
|
if (filename.length === 0 || filename === "." || filename === ".." || /[\\/]/u.test(filename)) {
|
|
68
110
|
throw new CliError("--next must be a non-empty single path segment.");
|
|
@@ -92,33 +134,55 @@ function resolveTicket(ctx, options) {
|
|
|
92
134
|
};
|
|
93
135
|
return resolveTicketDir(ctx.cwd, options.id, { dryRun: options.dryRun });
|
|
94
136
|
}
|
|
95
|
-
function
|
|
137
|
+
function writeNextReport(ctx, options, result, filename) {
|
|
138
|
+
const names = result.entries.map((entry) => entry.name);
|
|
139
|
+
const { cycleLetter, fileNumber } = nextFilePosition(names, options.newCycle);
|
|
140
|
+
const prefix = `${cycleLetter}${fileNumber}`;
|
|
141
|
+
const report = {
|
|
142
|
+
TICKET_DIR: `${relative(ctx.cwd, result.dir)}/`,
|
|
143
|
+
CYCLE_LETTER: cycleLetter,
|
|
144
|
+
FILE_NUMBER: fileNumber,
|
|
145
|
+
...(filename === true ? { FILE_PREFIX: prefix } : { FILE_NAME: `${prefix}-${filename}` }),
|
|
146
|
+
};
|
|
147
|
+
if (options.json) {
|
|
148
|
+
ctx.stdout.write(`${JSON.stringify(report, undefined, 2)}\n`);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const lines = Object.entries(report).map(([name, value]) => `- ${name}: \`${value}\``);
|
|
152
|
+
ctx.stdout.write(`${lines.join("\n")}\n`);
|
|
153
|
+
}
|
|
154
|
+
function jsonReport(ctx, options, result) {
|
|
96
155
|
return {
|
|
97
|
-
|
|
98
|
-
|
|
156
|
+
TICKET_ID: result.id,
|
|
157
|
+
TICKET_DIR: `${relative(ctx.cwd, result.dir)}/`,
|
|
99
158
|
state: result.state,
|
|
100
159
|
...(options.branch === undefined ? {} : { branch: options.branch }),
|
|
101
|
-
entries: result.entries
|
|
102
|
-
|
|
160
|
+
entries: result.entries.map((entry) => ({
|
|
161
|
+
...entry,
|
|
162
|
+
modifiedAt: entry.modifiedAt.toISOString(),
|
|
163
|
+
})),
|
|
103
164
|
};
|
|
104
165
|
}
|
|
105
|
-
function renderReport(ctx, options, result
|
|
166
|
+
function renderReport(ctx, options, result) {
|
|
106
167
|
const reservation = options.side && options.dryRun ? " (would be reserved)" : "";
|
|
107
168
|
const deduction = options.branch === undefined ? "" : ` (deduced from branch ${options.branch})`;
|
|
108
169
|
const directoryState = renderDirectoryState(result.state, options.dryRun);
|
|
109
170
|
const directory = `${relative(ctx.cwd, result.dir)}/`;
|
|
110
171
|
const lines = [
|
|
111
|
-
|
|
112
|
-
|
|
172
|
+
`- TICKET_ID: \`${result.id}\`${reservation}${deduction}`,
|
|
173
|
+
`- TICKET_DIR: \`${directory}\`${directoryState}`,
|
|
113
174
|
];
|
|
114
175
|
if (result.entries.length === 0)
|
|
115
176
|
lines.push("Entries: (none)");
|
|
116
177
|
else
|
|
117
|
-
lines.push("Entries:", ...result.entries.map((entry) => ` ${entry}`));
|
|
118
|
-
if (next !== undefined)
|
|
119
|
-
lines.push(`Next file: ${relative(ctx.cwd, join(result.dir, next))}`);
|
|
178
|
+
lines.push("Entries:", ...result.entries.map((entry) => ` ${renderEntry(entry)}`));
|
|
120
179
|
return `${lines.join("\n")}\n`;
|
|
121
180
|
}
|
|
181
|
+
function renderEntry(entry) {
|
|
182
|
+
if (entry.size === undefined)
|
|
183
|
+
return entry.name;
|
|
184
|
+
return `${entry.name} (${formatSize(entry.size)}, ${formatLocalTimestamp(entry.modifiedAt)})`;
|
|
185
|
+
}
|
|
122
186
|
function renderDirectoryState(state, dryRun) {
|
|
123
187
|
if (state === "existing")
|
|
124
188
|
return "";
|
package/dist/conventions.js
CHANGED
|
@@ -38,9 +38,9 @@ function renderCommits(ctx) {
|
|
|
38
38
|
}
|
|
39
39
|
export function commitSubject(commit) {
|
|
40
40
|
if (commit.ticketReference === "bracketed")
|
|
41
|
-
return { subject: "`type: [
|
|
41
|
+
return { subject: "`type: [TICKET_ID] summary`", side: "`type: summary` for `side-N`" };
|
|
42
42
|
if (commit.ticketReference === "bracketedHash")
|
|
43
|
-
return { subject: "`type: [#
|
|
43
|
+
return { subject: "`type: [#TICKET_ID] summary`", side: "`type: summary` for `side-N`" };
|
|
44
44
|
return { subject: "`type: summary`" };
|
|
45
45
|
}
|
|
46
46
|
function renderPlans(ctx) {
|
package/dist/format.d.ts
ADDED
package/dist/format.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const KIB = 1024;
|
|
2
|
+
const MIB = KIB * KIB;
|
|
3
|
+
export function formatSize(bytes) {
|
|
4
|
+
if (bytes < KIB)
|
|
5
|
+
return `${bytes} B`;
|
|
6
|
+
if (bytes < MIB)
|
|
7
|
+
return `${trimDecimal(bytes / KIB)} KiB`;
|
|
8
|
+
return `${trimDecimal(bytes / MIB)} MiB`;
|
|
9
|
+
}
|
|
10
|
+
function trimDecimal(value) {
|
|
11
|
+
return value.toFixed(1).replace(/\.0$/, "");
|
|
12
|
+
}
|
|
13
|
+
export function formatLocalTimestamp(date) {
|
|
14
|
+
const offsetMinutes = -date.getTimezoneOffset();
|
|
15
|
+
const sign = offsetMinutes < 0 ? "-" : "+";
|
|
16
|
+
const offset = Math.abs(offsetMinutes);
|
|
17
|
+
const day = `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
|
18
|
+
const time = `${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
19
|
+
return `${day}T${time}${sign}${pad(Math.floor(offset / 60))}:${pad(offset % 60)}`;
|
|
20
|
+
}
|
|
21
|
+
function pad(value) {
|
|
22
|
+
return String(value).padStart(2, "0");
|
|
23
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join, relative } from "node:path";
|
|
3
|
+
import { CliError } from "../cli-error.js";
|
|
4
|
+
import { formatLocalTimestamp, formatSize } from "../format.js";
|
|
5
|
+
const MAX_FILE_BYTES = 64 * 1024;
|
|
6
|
+
const MAX_OUTPUT_BYTES = 30 * 1024;
|
|
7
|
+
const PLAN_FILE = /^[A-Z]\d+-(?:main-plan|plan-.*)\.md$/;
|
|
8
|
+
const OMISSION_NOTICE = `Content omitted: over the ${formatSize(MAX_FILE_BYTES)} limit.`;
|
|
9
|
+
const TOO_LARGE_NOTICE = `History too large to print (over the ${formatSize(MAX_OUTPUT_BYTES)} budget). Read the relevant files among the entries above rather than all of them.`;
|
|
10
|
+
export function renderCatchup(cwd, ticket, report) {
|
|
11
|
+
const files = ticket.entries.flatMap((entry) => catchupFile(cwd, ticket.dir, entry));
|
|
12
|
+
if (files.length === 0)
|
|
13
|
+
return `${report}\nNo Markdown files to load.\n`;
|
|
14
|
+
const outputBytes = files.reduce((total, file) => total + sectionBytes(file), Buffer.byteLength(report) + 1);
|
|
15
|
+
if (outputBytes > MAX_OUTPUT_BYTES)
|
|
16
|
+
return `${report}\n${TOO_LARGE_NOTICE}\n`;
|
|
17
|
+
return `${report}\n${files.map((file) => renderSection(cwd, file)).join("")}`;
|
|
18
|
+
}
|
|
19
|
+
function catchupFile(cwd, dir, entry) {
|
|
20
|
+
if (entry.size === undefined || !isHistoryFile(entry.name))
|
|
21
|
+
return [];
|
|
22
|
+
return [
|
|
23
|
+
{
|
|
24
|
+
path: relative(cwd, join(dir, entry.name)),
|
|
25
|
+
size: entry.size,
|
|
26
|
+
modified: formatLocalTimestamp(entry.modifiedAt),
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
function isHistoryFile(name) {
|
|
31
|
+
return name.endsWith(".md") && (name.endsWith(".summary.md") || !PLAN_FILE.test(name));
|
|
32
|
+
}
|
|
33
|
+
function sectionBytes(file) {
|
|
34
|
+
const body = isOversized(file) ? Buffer.byteLength(OMISSION_NOTICE) : file.size;
|
|
35
|
+
return Buffer.byteLength(wrapSection(file, "")) + body;
|
|
36
|
+
}
|
|
37
|
+
function isOversized(file) {
|
|
38
|
+
return file.size > MAX_FILE_BYTES;
|
|
39
|
+
}
|
|
40
|
+
function wrapSection(file, body) {
|
|
41
|
+
return `<file path="${file.path}" modified="${file.modified}">\n${body}\n</file>\n\n`;
|
|
42
|
+
}
|
|
43
|
+
function renderSection(cwd, file) {
|
|
44
|
+
const body = isOversized(file) ? OMISSION_NOTICE : readBody(cwd, file);
|
|
45
|
+
return wrapSection(file, body.replace(/\n+$/, ""));
|
|
46
|
+
}
|
|
47
|
+
function readBody(cwd, file) {
|
|
48
|
+
try {
|
|
49
|
+
return readFileSync(join(cwd, file.path), "utf-8");
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
53
|
+
throw new CliError(`Cannot load catchup file ${file.path}: ${detail}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
package/dist/plans/ticket.d.ts
CHANGED
|
@@ -2,7 +2,13 @@ export interface ResolvedTicketDir {
|
|
|
2
2
|
id: string;
|
|
3
3
|
dir: string;
|
|
4
4
|
state: "existing" | "created" | "restored";
|
|
5
|
-
entries:
|
|
5
|
+
entries: TicketEntry[];
|
|
6
|
+
}
|
|
7
|
+
/** `name` ends with a slash for a directory, which has no `size`. */
|
|
8
|
+
export interface TicketEntry {
|
|
9
|
+
name: string;
|
|
10
|
+
size?: number;
|
|
11
|
+
modifiedAt: Date;
|
|
6
12
|
}
|
|
7
13
|
export interface ResolveTicketOptions {
|
|
8
14
|
dryRun: boolean;
|
|
@@ -24,8 +30,12 @@ export type TicketDetection = {
|
|
|
24
30
|
export declare function resolveTicketDir(cwd: string, id: string, { dryRun }: ResolveTicketOptions): ResolvedTicketDir;
|
|
25
31
|
export declare function reserveSideTicket(cwd: string): string;
|
|
26
32
|
export declare function peekSideTicket(cwd: string): string;
|
|
27
|
-
export
|
|
28
|
-
|
|
33
|
+
export interface NextFilePosition {
|
|
34
|
+
cycleLetter: string;
|
|
35
|
+
fileNumber: number;
|
|
36
|
+
}
|
|
37
|
+
export declare function nextFilePosition(entries: readonly string[], newCycle: boolean): NextFilePosition;
|
|
38
|
+
export declare function listEntries(dir: string): TicketEntry[];
|
|
29
39
|
export declare function isPathSafeTicketId(id: string): boolean;
|
|
30
40
|
export declare function validateTicketId(id: string, pattern?: string): void;
|
|
31
41
|
export declare function detectTicketFromBranch(cwd: string, pattern: string): TicketDetection;
|
package/dist/plans/ticket.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readdirSync, renameSync, statSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
1
|
+
import { existsSync, lstatSync, mkdirSync, readdirSync, renameSync, statSync, } from "node:fs";
|
|
2
|
+
import { basename, join } from "node:path";
|
|
3
3
|
import { CliError } from "../cli-error.js";
|
|
4
4
|
import { isNodeError } from "../errors.js";
|
|
5
5
|
import { gitOutputOrUndefined } from "../git.js";
|
|
@@ -7,6 +7,7 @@ import { archivesDir, plansDir } from "./layout.js";
|
|
|
7
7
|
const FILE_PREFIX = /^([A-Z])(\d+)-/;
|
|
8
8
|
const SIDE_TICKET = /^side-(\d+)$/;
|
|
9
9
|
const PATH_SAFE_TICKET = /^[A-Za-z0-9._-]+$/;
|
|
10
|
+
const ENTRY_ORDER = new Intl.Collator("en", { numeric: true });
|
|
10
11
|
export function resolveTicketDir(cwd, id, { dryRun }) {
|
|
11
12
|
const dir = join(plansDir(cwd), id);
|
|
12
13
|
if (existsSync(dir))
|
|
@@ -63,25 +64,39 @@ function readEntries(dir) {
|
|
|
63
64
|
return [];
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
|
-
export function
|
|
67
|
+
export function nextFilePosition(entries, newCycle) {
|
|
67
68
|
const prefixes = entries.flatMap((entry) => {
|
|
68
69
|
const match = FILE_PREFIX.exec(entry);
|
|
69
70
|
return match ? [{ cycle: match[1], number: Number(match[2]) }] : [];
|
|
70
71
|
});
|
|
71
72
|
if (prefixes.length === 0)
|
|
72
|
-
return
|
|
73
|
+
return { cycleLetter: "A", fileNumber: 1 };
|
|
73
74
|
const highestCycle = prefixes.reduce((highest, prefix) => (prefix.cycle > highest ? prefix.cycle : highest), "A");
|
|
74
75
|
if (newCycle)
|
|
75
|
-
return
|
|
76
|
+
return { cycleLetter: String.fromCharCode(highestCycle.charCodeAt(0) + 1), fileNumber: 1 };
|
|
76
77
|
const highestNumber = Math.max(...prefixes.filter(({ cycle }) => cycle === highestCycle).map(({ number }) => number));
|
|
77
|
-
return
|
|
78
|
+
return { cycleLetter: highestCycle, fileNumber: highestNumber + 1 };
|
|
78
79
|
}
|
|
79
80
|
export function listEntries(dir) {
|
|
80
81
|
if (!existsSync(dir) || !statSync(dir).isDirectory())
|
|
81
82
|
return [];
|
|
82
|
-
return readdirSync(dir
|
|
83
|
-
.map((
|
|
84
|
-
.toSorted();
|
|
83
|
+
return readdirSync(dir)
|
|
84
|
+
.map((name) => describeEntry(join(dir, name)))
|
|
85
|
+
.toSorted((left, right) => ENTRY_ORDER.compare(left.name, right.name));
|
|
86
|
+
}
|
|
87
|
+
function describeEntry(path) {
|
|
88
|
+
const stat = statOrLink(path);
|
|
89
|
+
if (stat.isDirectory())
|
|
90
|
+
return { name: `${basename(path)}/`, modifiedAt: stat.mtime };
|
|
91
|
+
return { name: basename(path), size: stat.size, modifiedAt: stat.mtime };
|
|
92
|
+
}
|
|
93
|
+
function statOrLink(path) {
|
|
94
|
+
try {
|
|
95
|
+
return statSync(path);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return lstatSync(path);
|
|
99
|
+
}
|
|
85
100
|
}
|
|
86
101
|
export function isPathSafeTicketId(id) {
|
|
87
102
|
return id !== "." && !id.includes("..") && PATH_SAFE_TICKET.test(id);
|
package/dist/protocols.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const PROTOCOLS: readonly ["spec", "plan", "aad", "
|
|
1
|
+
export declare const PROTOCOLS: readonly ["spec", "plan", "aad", "merge", "review", "description"];
|
|
2
2
|
export type Protocol = (typeof PROTOCOLS)[number];
|
package/dist/protocols.js
CHANGED
package/dist/skills.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const STUB_SKILLS: readonly ["alignfirst", "alspec", "alplan", "al", "alcatchup", "almerge", "alreview", "aldescription"];
|
|
1
|
+
export declare const STUB_SKILLS: readonly ["alignfirst", "alspec", "alplan", "al", "alcatchup", "alcatchupaad", "alcatchupspec", "almerge", "alreview", "aldescription"];
|
|
2
2
|
export declare const SKILL_ROOTS: readonly [".agents/skills", ".claude/skills", ".codex/skills"];
|
|
3
3
|
export interface InstalledSkill {
|
|
4
4
|
root: string;
|
package/dist/skills.js
CHANGED
package/package.json
CHANGED
package/templates/guide/core.md
CHANGED
|
@@ -1,54 +1,27 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Shared Conventions
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## Ticket directory
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
TICKET_DIR holds a ticket's work files and includes a trailing slash. TICKET_ID is the external ticket ID or `side-N` for work without an external ticket.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- **Implementation Plans** (_plan_, or _alplan_): `{{CMD}} guide plan --protocol-only`
|
|
9
|
-
- **Align-and-Do Protocol** (_AAD_): `{{CMD}} guide aad --protocol-only`
|
|
10
|
-
- **Catch Up** (_catchup_, or _alcatchup_): `{{CMD}} guide catchup --protocol-only`
|
|
11
|
-
- **Merge** (_merge_, or _almerge_): `{{CMD}} guide merge --protocol-only`
|
|
12
|
-
- **Code Review** (_alreview_): `{{CMD}} guide review --protocol-only`
|
|
13
|
-
- **Description** (_aldescription_): `{{CMD}} guide description --protocol-only`
|
|
7
|
+
{{TICKET_DETECTION}}
|
|
14
8
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
TASK_DIR holds the work files of a ticket. `{{TICKET_CMD}}` prints it and lists its entries; it creates a missing directory and restores an archived one.
|
|
18
|
-
|
|
19
|
-
{{TICKET_CONTEXT}}
|
|
9
|
+
`{{TICKET_CMD}}` prints TICKET_DIR and its entries, creates a missing directory, and restores an archived one. Run it once to load the ticket directory context, unless `{{CMD}} ticket --catchup` already did.
|
|
20
10
|
|
|
21
11
|
{{PLANS_STATE}}
|
|
22
12
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
## File Naming Convention
|
|
13
|
+
When the user says there is no external ticket, run `{{CMD}} ticket --side` and use the returned TICKET_ID in subsequent ticket commands. Reuse an existing `side-N` directory when the user refers to earlier work. Omit the ticket ID from commit messages.
|
|
26
14
|
|
|
27
|
-
|
|
15
|
+
## Work files
|
|
28
16
|
|
|
29
|
-
|
|
17
|
+
Files use `{CYCLE_LETTER}{FILE_NUMBER}-{FILE_TYPE}.md`. FILE_PREFIX combines the cycle letter and the file number within that cycle. FILE_NAME includes the prefix and extension.
|
|
30
18
|
|
|
31
|
-
|
|
32
|
-
- `plan` - implementation plan
|
|
33
|
-
- `AAD.summary` - AAD summary document
|
|
34
|
-
- `description` - PR/MR description
|
|
35
|
-
- `review` - code review report
|
|
36
|
-
- `merge.summary` - merge conflicts resolution summary
|
|
19
|
+
Immediately before creating each file, run `{{TICKET_CMD}} --next <filename>` with the extension included. It returns TICKET_DIR, CYCLE_LETTER, FILE_NUMBER, and FILE_NAME. Append FILE_NAME to TICKET_DIR to get the file path, preserving the leading dot and existing slash.
|
|
37
20
|
|
|
38
|
-
|
|
21
|
+
With no filename, `{{TICKET_CMD}} --next` returns FILE_PREFIX instead of FILE_NAME. Add `--new-cycle` to either form when the protocol or user calls for a new cycle.
|
|
39
22
|
|
|
40
|
-
|
|
41
|
-
A1-spec.md
|
|
42
|
-
A2-plan.md
|
|
43
|
-
A3-AAD.summary.md
|
|
44
|
-
B1-spec.md
|
|
45
|
-
```
|
|
23
|
+
Neither form reserves a number. Write the file before requesting another filename. With `--json`, the same information uses the same field names.
|
|
46
24
|
|
|
47
|
-
|
|
25
|
+
Common file types are `spec`, `plan`, `AAD.summary`, `description`, `review`, and `merge.summary`. Use another type when needed.
|
|
48
26
|
|
|
49
|
-
|
|
50
|
-
- `{{TICKET_CMD}} --next <filename>` prints the path of the next file in the current cycle, the extension included (`--next spec.md` giving `A2-spec.md`).
|
|
51
|
-
- `--new-cycle` starts a new cycle.
|
|
52
|
-
- The protocol or the user decides whether to continue the current cycle or start a new one.
|
|
53
|
-
- Cycle letters and file numbers are internal. Never discuss them with the user.
|
|
54
|
-
- New file types are welcome.
|
|
27
|
+
Cycle letters and file numbers are internal. Never discuss them with the user.
|
|
@@ -8,8 +8,8 @@ AlignFirst is a set of collaborative protocols for working with a user on coding
|
|
|
8
8
|
|
|
9
9
|
The standard workflow for most tasks. It produces formal artifacts at each stage:
|
|
10
10
|
|
|
11
|
-
1. **Spec** (
|
|
12
|
-
2. **Plan** (
|
|
11
|
+
1. **Spec** (`alspec` alias): Investigate the codebase, discuss with the user, then write a technical specification. There are usually several back-and-forths before the spec is written.
|
|
12
|
+
2. **Plan** (`alplan` alias): Read the spec, investigate further, then write implementation plan(s). The plan is a self-contained prompt for the implementing agent.
|
|
13
13
|
3. **Execute**: A new session implements the plan and writes a handover document summarizing the changes.
|
|
14
14
|
|
|
15
15
|
After execution, additional rounds of AAD (see below) can address follow-up fixes or adjustments.
|
|
@@ -18,7 +18,7 @@ After execution, additional rounds of AAD (see below) can address follow-up fixe
|
|
|
18
18
|
|
|
19
19
|
For smaller tasks that don't justify a formal spec and plan. Everything happens in one session: investigate, discuss, implement, summarize.
|
|
20
20
|
|
|
21
|
-
Use AAD (
|
|
21
|
+
Use AAD (`al` alias) when:
|
|
22
22
|
|
|
23
23
|
- The task is small or well-understood
|
|
24
24
|
- It's a follow-up change after a plan has already been executed
|
|
@@ -32,19 +32,21 @@ Use Spec-Plan-Execute when:
|
|
|
32
32
|
|
|
33
33
|
## Catch Up
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
`{{CMD}} ticket --catchup` loads the ticket's history: its requests, specs, reviews and summaries, without the plans. When the history is too large, the command lists the files instead and the agent reads the relevant ones.
|
|
36
|
+
|
|
37
|
+
The `alcatchup` alias loads this context, then follows the user's instructions or returns a short synthesis. `alcatchupaad` and `alcatchupspec` load it, then start AAD or a specification.
|
|
36
38
|
|
|
37
39
|
## Description
|
|
38
40
|
|
|
39
|
-
A standalone utility (
|
|
41
|
+
A standalone utility (`aldescription` alias). It reads specs and summaries that have been generated for a ticket and produces a concise description of what was implemented. Typically used to generate a PR/MR description once the work is done.
|
|
40
42
|
|
|
41
43
|
## Code Review
|
|
42
44
|
|
|
43
|
-
A standalone utility (
|
|
45
|
+
A standalone utility (`alreview` alias). It compares the current branch to a base branch (defaults to the repo's default branch) and runs parallel reviewers, each with its own perspective: intent, correctness, change safety, code quality. Ecosystem modules (strict TypeScript, JavaScript, Python) sharpen the language-specific checks. The findings are merged into a concise review report.
|
|
44
46
|
|
|
45
47
|
## Merge
|
|
46
48
|
|
|
47
|
-
A standalone utility (
|
|
49
|
+
A standalone utility (`almerge` alias). After a merge or rebase, the agent investigates both sides, resolves the conflicts (with a special case for lock files), and writes a brief summary of the resolutions.
|
|
48
50
|
|
|
49
51
|
## Typical Lifecycle of a Ticket
|
|
50
52
|
|
|
@@ -2,12 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Pre-requisites
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
- the TASK_DIR — run `{{TICKET_CMD}}` (`{{CMD}} ticket --side` when there is no ticket)
|
|
8
|
-
- the CYCLE_LETTER and FILE_NUMBER — continue the current cycle: `{{TICKET_CMD}} --next AAD.summary.md` prints the file to create
|
|
9
|
-
|
|
10
|
-
Identify and state these values before starting the protocol.
|
|
5
|
+
Run `{{TICKET_CMD}}` once to identify TICKET_DIR and load the ticket directory context (`{{CMD}} ticket --side` when there is no external ticket).
|
|
11
6
|
|
|
12
7
|
---
|
|
13
8
|
|
|
@@ -42,9 +37,7 @@ Do not use your question tool. Always ask in plain text. Your questions will be
|
|
|
42
37
|
|
|
43
38
|
## 3. Act
|
|
44
39
|
|
|
45
|
-
When you and the user agree, create the summary file
|
|
46
|
-
|
|
47
|
-
Compose the filename using the current CYCLE_LETTER and the bumped FILE_NUMBER, then append `-AAD.summary.md`. For example, if the last file is `E5-plan-something.md`, create `E6-AAD.summary.md`. Do not overwrite an existing file.
|
|
40
|
+
When you and the user agree, run `{{TICKET_CMD}} --next AAD.summary.md` to continue the current cycle. Append FILE_NAME to TICKET_DIR, then immediately create the summary file at that path. Do not overwrite an existing file. Start implementing after creating it.
|
|
48
41
|
|
|
49
42
|
Maintain the file as a **live report** while you work.
|
|
50
43
|
|
|
@@ -2,18 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## Pre-requisites
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
- the TASK_DIR — run `{{TICKET_CMD}}` (`{{CMD}} ticket --side` when there is no ticket)
|
|
8
|
-
- the CYCLE_LETTER and FILE_NUMBER — start a new cycle: `{{TICKET_CMD}} --next description.md --new-cycle` prints the file to create
|
|
9
|
-
|
|
10
|
-
Identify and state these values before starting the protocol.
|
|
5
|
+
Run `{{TICKET_CMD}}` once to identify TICKET_DIR and load the ticket directory context (`{{CMD}} ticket --side` when there is no external ticket).
|
|
11
6
|
|
|
12
7
|
## Steps
|
|
13
8
|
|
|
14
|
-
1.
|
|
15
|
-
2.
|
|
16
|
-
3. If a previous `*description.md` file exists in the
|
|
9
|
+
1. Run `{{TICKET_CMD}} --next description.md --new-cycle` to start a new cycle.
|
|
10
|
+
2. Append FILE_NAME to TICKET_DIR, then immediately create the description at that path with just the header. Creating the file reserves the filename.
|
|
11
|
+
3. If a previous `*description.md` file exists in the TICKET_DIR, find the latest one. Only read `*spec.md` and `*summary.md` files that come *after* it — earlier work is already covered.
|
|
17
12
|
Otherwise, read all `*spec.md` and `*summary.md` files.
|
|
18
13
|
4. Write the commit message and description into your file.
|
|
19
14
|
|
|
@@ -2,12 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Pre-requisites
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
- the TASK_DIR — run `{{TICKET_CMD}}` (`{{CMD}} ticket --side` when there is no ticket)
|
|
8
|
-
- the CYCLE_LETTER and FILE_NUMBER — continue the current cycle: `{{TICKET_CMD}} --next merge.summary.md` prints the file to create
|
|
9
|
-
|
|
10
|
-
Identify and state these values before starting the protocol.
|
|
5
|
+
Run `{{TICKET_CMD}}` once to identify TICKET_DIR and load the ticket directory context (`{{CMD}} ticket --side` when there is no external ticket).
|
|
11
6
|
|
|
12
7
|
---
|
|
13
8
|
|
|
@@ -25,7 +20,7 @@ Take the time to understand how things work in the incoming branch and in the cu
|
|
|
25
20
|
|
|
26
21
|
## 3. Resolve
|
|
27
22
|
|
|
28
|
-
|
|
23
|
+
Run `{{TICKET_CMD}} --next merge.summary.md` to continue the current cycle. Append FILE_NAME to TICKET_DIR, then immediately create the summary at that path. Log each notable resolution in it as you resolve (see step 5 for the expected content).
|
|
29
24
|
|
|
30
25
|
Resolve the conflicts properly — preserve both intents whenever possible. Do not blindly accept one side.
|
|
31
26
|
|
|
@@ -2,15 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## Pre-requisites
|
|
4
4
|
|
|
5
|
-
### Determine
|
|
5
|
+
### Determine TICKET_DIR and the spec file
|
|
6
6
|
|
|
7
7
|
You need:
|
|
8
8
|
|
|
9
|
-
- the
|
|
10
|
-
-
|
|
11
|
-
- a **spec file** in the TASK_DIR
|
|
9
|
+
- the TICKET_DIR and ticket directory context — run `{{TICKET_CMD}}` once (`{{CMD}} ticket --side` when there is no external ticket)
|
|
10
|
+
- a **spec file** in the TICKET_DIR
|
|
12
11
|
|
|
13
|
-
Identify and
|
|
12
|
+
Identify TICKET_DIR and the spec file before starting the protocol. If either is missing, STOP AND ASK THE USER.
|
|
14
13
|
|
|
15
14
|
## Phases
|
|
16
15
|
|
|
@@ -113,13 +112,13 @@ Example:
|
|
|
113
112
|
_For all plans (single or specialized)_, add a final step named "Write a Handover Document" with this content:
|
|
114
113
|
|
|
115
114
|
```markdown
|
|
116
|
-
Write a **handover document**. This document must contain the list of all files you updated. Also, summarize the changes made in a very concise way. Add only relevant information that will help your teammates understand what's new. Do not mention obvious information. It's not a course or a tutorial, if there is nothing to explain, then do not explain.
|
|
115
|
+
Write a **handover document**. This document must contain the list of all files you updated. Also, summarize the changes made in a very concise way. Add only relevant information that will help your teammates understand what's new. Do not mention obvious information. It's not a course or a tutorial, if there is nothing to explain, then do not explain. Create its path by replacing the final `.md` in `{PLAN_FILE_PATH}` with `.summary.md`, then write the handover there. Ignore lint errors (formatting issues) in this file. At the end, give the path of this handover file to the user.
|
|
117
116
|
```
|
|
118
117
|
|
|
119
118
|
Note:
|
|
120
119
|
|
|
121
120
|
- This is a regular step, it should be numbered like the other steps. For example, if your plan has 5 steps, this becomes step 6.
|
|
122
|
-
- Replace "{PLAN_FILE_PATH}" with the
|
|
121
|
+
- Replace "{PLAN_FILE_PATH}" with the complete plan file path, such as `.plans/123/A2-plan-backend.md`. Its handover path is `.plans/123/A2-plan-backend.summary.md`.
|
|
123
122
|
|
|
124
123
|
### 3.5 Common Footer for All Plans
|
|
125
124
|
|
|
@@ -193,7 +192,7 @@ Write a **main plan handover document**. This document should:
|
|
|
193
192
|
- State "Completed" if the plan was executed successfully
|
|
194
193
|
- Detail any issues encountered during execution
|
|
195
194
|
|
|
196
|
-
Keep this handover very short. Do not combine or repeat the content of individual handovers.
|
|
195
|
+
Keep this handover very short. Do not combine or repeat the content of individual handovers. Create its path by replacing the final `.md` in `{PLAN_FILE_PATH}` with `.summary.md`, then write the handover there. Ignore lint errors (formatting issues) in this file.
|
|
197
196
|
|
|
198
197
|
---
|
|
199
198
|
|
|
@@ -204,34 +203,33 @@ Do not trust this plan blindly. Be sure you understand the codebase and all spec
|
|
|
204
203
|
|
|
205
204
|
Note:
|
|
206
205
|
|
|
207
|
-
- Replace "{PLAN_FILE_PATH}" with the
|
|
206
|
+
- Replace "{PLAN_FILE_PATH}" with the complete plan file path, such as `.plans/123/A2-main-plan.md`. Its handover path is `.plans/123/A2-main-plan.summary.md`.
|
|
208
207
|
|
|
209
208
|
## Phase 5. Writing Phase
|
|
210
209
|
|
|
211
210
|
Write the plan file(s) according to the determined structure:
|
|
212
211
|
|
|
213
|
-
|
|
212
|
+
Continue the current cycle. Immediately before writing each file, run `{{TICKET_CMD}} --next plan.md`, `{{TICKET_CMD}} --next main-plan.md`, or `{{TICKET_CMD}} --next plan-<descriptor>.md`. Append FILE_NAME to TICKET_DIR to get its path. Write that file before requesting the next one.
|
|
214
213
|
|
|
215
214
|
**Single Plan**:
|
|
216
215
|
|
|
217
|
-
- **Single plan**: `{
|
|
216
|
+
- **Single plan**: `{TICKET_DIR}{CYCLE_LETTER}{FILE_NUMBER}-plan.md`
|
|
218
217
|
- Example: `.plans/123/A2-plan.md`
|
|
219
218
|
- Handover: `.plans/123/A2-plan.summary.md`
|
|
220
219
|
- No main plan needed
|
|
221
220
|
|
|
222
221
|
**Multiple Plans**:
|
|
223
222
|
|
|
224
|
-
- **Main plan**: `{
|
|
223
|
+
- **Main plan**: `{TICKET_DIR}{CYCLE_LETTER}{FILE_NUMBER}-main-plan.md`
|
|
225
224
|
- Example: `.plans/123/A2-main-plan.md`
|
|
226
225
|
- Handover: `.plans/123/A2-main-plan.summary.md` (written after all specialized plans complete)
|
|
227
|
-
- **Specialized plans**: `{
|
|
226
|
+
- **Specialized plans**: `{TICKET_DIR}{CYCLE_LETTER}{FILE_NUMBER}-plan-{DESCRIPTOR}.md`
|
|
228
227
|
- Use a descriptive name as `{DESCRIPTOR}` (e.g., work scope, stack area)
|
|
229
228
|
- Example: `.plans/123/A3-plan-api.md`, `.plans/123/A4-plan-ui.md`
|
|
230
229
|
- Handovers: `.plans/123/A3-plan-api.summary.md`, etc.
|
|
231
230
|
|
|
232
231
|
**Important**:
|
|
233
232
|
|
|
234
|
-
- Increment FILE_NUMBER for each plan file
|
|
235
233
|
- Use lowercase, hyphenated descriptors for plan names (work scope descriptor)
|
|
236
234
|
- When multiple plans are created, the main plan should be written first and have the lowest FILE_NUMBER
|
|
237
235
|
- Be careful never to overwrite an existing file
|
|
@@ -4,19 +4,18 @@
|
|
|
4
4
|
|
|
5
5
|
You need:
|
|
6
6
|
|
|
7
|
-
- the
|
|
8
|
-
- the CYCLE_LETTER and FILE_NUMBER — start a new cycle: `{{TICKET_CMD}} --next review.md --new-cycle` prints the file to create
|
|
7
|
+
- the TICKET_DIR and ticket directory context — run `{{TICKET_CMD}}` once (`{{CMD}} ticket --side` when there is no external ticket)
|
|
9
8
|
- {{BASE_BRANCH_RULE}}
|
|
10
9
|
|
|
11
|
-
Identify and
|
|
10
|
+
Identify TICKET_DIR and the base branch before starting the protocol.
|
|
12
11
|
|
|
13
12
|
## Overview
|
|
14
13
|
|
|
15
14
|
We need a code review for this branch, compared to the base branch. A code review, above all, guarantees that the codebase stays healthy.
|
|
16
15
|
|
|
17
|
-
You are the orchestrator: you scope the work, run one reviewer subagent per perspective, then merge their findings into a single report. Reviewers work with fresh eyes — they derive intent from the code and the diff. Neither you nor the reviewers read specs, plans, summaries, or any file content in
|
|
16
|
+
You are the orchestrator: you scope the work, run one reviewer subagent per perspective, then merge their findings into a single report. Reviewers work with fresh eyes — they derive intent from the code and the diff. Neither you nor the reviewers read specs, plans, summaries, or any file content in TICKET_DIR.
|
|
18
17
|
|
|
19
|
-
Before starting,
|
|
18
|
+
Before starting, run `{{TICKET_CMD}} --next review.md --new-cycle` to start a new cycle. Append FILE_NAME to TICKET_DIR, then immediately create the report at that path with just the header. Creating the file reserves the filename. Write the report into it at the end.
|
|
20
19
|
|
|
21
20
|
## Phase 1. Scoping
|
|
22
21
|
|
|
@@ -2,12 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Pre-requisites
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
- the TASK_DIR — run `{{TICKET_CMD}}` (`{{CMD}} ticket --side` when there is no ticket)
|
|
8
|
-
- the CYCLE_LETTER and FILE_NUMBER — start a new cycle: `{{TICKET_CMD}} --next spec.md --new-cycle` prints the file to create
|
|
9
|
-
|
|
10
|
-
Identify and state these values before starting the protocol.
|
|
5
|
+
Run `{{TICKET_CMD}}` once to identify TICKET_DIR and load the ticket directory context (`{{CMD}} ticket --side` when there is no external ticket).
|
|
11
6
|
|
|
12
7
|
## Phases
|
|
13
8
|
|
|
@@ -49,7 +44,7 @@ Do not use your question tool. Always ask in plain text. Your questions will be
|
|
|
49
44
|
|
|
50
45
|
## Phase 3. Specification Phase
|
|
51
46
|
|
|
52
|
-
After the user approves your proposal,
|
|
47
|
+
After the user approves your proposal, run `{{TICKET_CMD}} --next spec.md --new-cycle` to start a new cycle. Append FILE_NAME to TICKET_DIR, then immediately write the specification at that path. Do not overwrite an existing file.
|
|
53
48
|
|
|
54
49
|
- After the title, include a suggested commit message {{COMMIT_RULE}}. The shorter the better. Then list the required documentation and skills. List each doc file individually — never a folder. Always exclude `alignfirst` from skills. Omit any field with nothing to list. Example:
|
|
55
50
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# AlignFirst Guide
|
|
2
|
+
|
|
3
|
+
Follow the requested protocol if its guide is already in context. Otherwise, load it with the command below. Each named guide includes its protocol and shared conventions; add `--protocol-only` when those conventions are already in context.
|
|
4
|
+
|
|
5
|
+
## Choose a protocol
|
|
6
|
+
|
|
7
|
+
Use spec → plan → execution for most tasks, especially when the design is uncertain. Use AAD for small changes or follow-up work. Execute a written plan in a fresh agent session.
|
|
8
|
+
|
|
9
|
+
| Protocol | Purpose | Command |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| Specification (`alspec` alias) | Investigate, discuss, and write a technical specification. | `{{CMD}} guide spec` |
|
|
12
|
+
| Planning (`alplan` alias) | Turn a specification into implementation plans. | `{{CMD}} guide plan` |
|
|
13
|
+
| Align-and-Do (`AAD`, `al` aliases) | Investigate, agree, implement, and summarize a small change. | `{{CMD}} guide aad` |
|
|
14
|
+
| Merge (`almerge` alias) | Merge an incoming branch and resolve conflicts. | `{{CMD}} guide merge` |
|
|
15
|
+
| Review (`alreview` alias) | Review committed branch changes against a base branch. | `{{CMD}} guide review` |
|
|
16
|
+
| Description (`aldescription` alias) | Write a concise description of implemented work. | `{{CMD}} guide description` |
|
|
17
|
+
| Catch up (`alcatchup`, `alcatchupaad`, `alcatchupspec` aliases) | Load the ticket history, then continue, start AAD, or start a specification. | `{{CMD}} ticket --catchup` |
|
|
18
|
+
|
|
19
|
+
For more detail on workflows and the ticket lifecycle, read `{{CMD}} guide overview`.
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
# How to Catch Up With a Task
|
|
2
|
-
|
|
3
|
-
## Pre-requisites
|
|
4
|
-
|
|
5
|
-
You need the TASK_DIR — run `{{TICKET_CMD}}` (`{{CMD}} ticket --side` when there is no ticket).
|
|
6
|
-
|
|
7
|
-
## Steps
|
|
8
|
-
|
|
9
|
-
1. Determine TASK_DIR.
|
|
10
|
-
2. Read every `*request.md`, `*spec.md` and `*summary.md` file in TASK_DIR, in filename order.
|
|
11
|
-
3. When the user's message carries instructions, follow them with this context loaded, and write no synthesis. Otherwise reply with a short synthesis: what was requested, what was decided, what was done, what remains or is open.
|
|
12
|
-
|
|
13
|
-
This protocol writes no file.
|