alignfirst 0.1.0-beta.7 → 0.1.0

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 CHANGED
@@ -89,12 +89,12 @@ AlignFirst stores specifications, plans, and summaries in `.plans/<ticket-id>/`.
89
89
 
90
90
  ## Catchup output
91
91
 
92
- `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.
92
+ `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, with a notice telling the agent to read the relevant files and skip the plans.
93
93
 
94
94
  ## Updates
95
95
 
96
96
  ```sh
97
- npm update -g alignfirst
97
+ npm install -g alignfirst@latest
98
98
  npx skills update --global --yes
99
99
  ```
100
100
 
@@ -11,6 +11,8 @@ const USAGE = `Usage:
11
11
  {{FORM}} ticket --side [--next [<filename>]] [--new-cycle] [--json] [--dry-run]
12
12
  {{FORM}} ticket [<id>] --catchup
13
13
 
14
+ --next may repeat, each occurrence with a filename: the names are numbered in order.
15
+
14
16
  --catchup prints the ticket's Markdown files, plans excluded, summaries included.
15
17
  Files over 64 KiB are listed without content. Above 30 KiB of output, only the entry
16
18
  list is printed.
@@ -68,12 +70,10 @@ function parseTicketArgs(ctx, args, usage) {
68
70
  if (values.catchup &&
69
71
  (values.side || values.next !== undefined || values.json || values["dry-run"]))
70
72
  throw new CliError(`--catchup cannot be combined with --side, --next, --json, or --dry-run.\n\n${usage}`);
71
- if (normalized.filename !== undefined)
72
- validateNextFilename(normalized.filename);
73
73
  const resolution = resolveTicketId(ctx, positionals[0], values);
74
74
  return {
75
75
  ...resolution,
76
- next: values.next === undefined ? undefined : (normalized.filename ?? true),
76
+ next: values.next === undefined ? undefined : resolveNextRequest(normalized.requests, usage),
77
77
  newCycle: values["new-cycle"],
78
78
  json: values.json,
79
79
  dryRun: values["dry-run"],
@@ -82,7 +82,7 @@ function parseTicketArgs(ctx, args, usage) {
82
82
  };
83
83
  }
84
84
  function normalizeNextArgs(args) {
85
- const normalized = { args: [] };
85
+ const normalized = { args: [], requests: [] };
86
86
  for (let index = 0; index < args.length; ++index) {
87
87
  const arg = args[index];
88
88
  if (arg === "--") {
@@ -90,7 +90,7 @@ function normalizeNextArgs(args) {
90
90
  break;
91
91
  }
92
92
  if (arg.startsWith("--next=")) {
93
- normalized.filename = arg.slice("--next=".length);
93
+ normalized.requests.push(arg.slice("--next=".length));
94
94
  normalized.args.push("--next");
95
95
  continue;
96
96
  }
@@ -98,14 +98,25 @@ function normalizeNextArgs(args) {
98
98
  if (arg !== "--next")
99
99
  continue;
100
100
  const following = args[index + 1];
101
- delete normalized.filename;
102
101
  if (following !== undefined && !following.startsWith("-")) {
103
- normalized.filename = following;
102
+ normalized.requests.push(following);
104
103
  ++index;
105
104
  }
105
+ else
106
+ normalized.requests.push(undefined);
106
107
  }
107
108
  return normalized;
108
109
  }
110
+ function resolveNextRequest(requests, usage) {
111
+ if (requests.length === 1 && requests[0] === undefined)
112
+ return true;
113
+ const filenames = requests.filter((request) => request !== undefined);
114
+ if (filenames.length < requests.length)
115
+ throw new CliError(`A repeated --next requires a filename on each occurrence.\n\n${usage}`);
116
+ for (const filename of filenames)
117
+ validateNextFilename(filename);
118
+ return filenames;
119
+ }
109
120
  function validateNextFilename(filename) {
110
121
  if (filename.length === 0 || filename === "." || filename === ".." || /[\\/]/u.test(filename)) {
111
122
  throw new CliError("--next must be a non-empty single path segment.");
@@ -135,21 +146,26 @@ function resolveTicket(ctx, options) {
135
146
  };
136
147
  return resolveTicketDir(ctx.cwd, options.id, { dryRun: options.dryRun });
137
148
  }
138
- function writeNextReport(ctx, options, result, filename) {
149
+ function writeNextReport(ctx, options, result, request) {
139
150
  const names = result.entries.map((entry) => entry.name);
140
151
  const { cycleLetter, fileNumber } = nextFilePosition(names, options.newCycle);
141
- const prefix = `${cycleLetter}${fileNumber}`;
152
+ const fileName = (filename, offset) => `${cycleLetter}${fileNumber + offset}-${filename}`;
142
153
  const report = {
143
154
  TICKET_DIR: `${relative(ctx.cwd, result.dir)}/`,
144
155
  CYCLE_LETTER: cycleLetter,
145
- FILE_NUMBER: fileNumber,
146
- ...(filename === true ? { FILE_PREFIX: prefix } : { FILE_NAME: `${prefix}-${filename}` }),
156
+ ...(request === true
157
+ ? { FILE_NUMBER: fileNumber, FILE_PREFIX: `${cycleLetter}${fileNumber}` }
158
+ : request.length === 1
159
+ ? { FILE_NUMBER: fileNumber, FILE_NAME: fileName(request[0], 0) }
160
+ : { FILE_NAMES: request.map(fileName) }),
147
161
  };
148
162
  if (options.json) {
149
163
  ctx.stdout.write(`${JSON.stringify(report, undefined, 2)}\n`);
150
164
  return;
151
165
  }
152
- const lines = Object.entries(report).map(([name, value]) => `- ${name}: \`${value}\``);
166
+ const lines = Object.entries(report).map(([name, value]) => Array.isArray(value)
167
+ ? [`- ${name}:`, ...value.map((item) => ` - \`${item}\``)].join("\n")
168
+ : `- ${name}: \`${value}\``);
153
169
  ctx.stdout.write(`${lines.join("\n")}\n`);
154
170
  }
155
171
  function jsonReport(ctx, options, result) {
@@ -7,7 +7,7 @@ const MAX_FILE_BYTES = 64 * 1024;
7
7
  const MAX_OUTPUT_BYTES = 30 * 1024;
8
8
  const PLAN_FILE = /^[A-Z]\d+-(?:main-plan|plan-.*)\.md$/;
9
9
  const OMISSION_NOTICE = `Content omitted: over the ${formatSize(MAX_FILE_BYTES)} limit.`;
10
- 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
+ const TOO_LARGE_NOTICE = `History too large to print (over the ${formatSize(MAX_OUTPUT_BYTES)} budget). Read the relevant files among the entries above, skipping the plans (\`*-plan.md\`, \`*-main-plan.md\`, \`*-plan-*.md\`): their \`.summary.md\` files cover them.`;
11
11
  export function renderCatchup(cwd, ticket, report) {
12
12
  const files = ticket.entries.flatMap((entry) => catchupFile(cwd, ticket.dir, entry));
13
13
  if (files.length === 0)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alignfirst",
3
- "version": "0.1.0-beta.7",
3
+ "version": "0.1.0",
4
4
  "license": "CC0-1.0",
5
5
  "author": "Thomas MUR",
6
6
  "description": "The AlignFirst CLI: protocols, plans and docs in one command.",
@@ -34,7 +34,7 @@
34
34
  "access": "public"
35
35
  },
36
36
  "dependencies": {
37
- "@paleo/docmap": "~0.10.0-beta.1",
37
+ "@paleo/docmap": "~0.10.0",
38
38
  "arktype": "^2.2.3",
39
39
  "semver": "^7.8.5"
40
40
  },
@@ -18,9 +18,9 @@ Files use `{CYCLE_LETTER}{FILE_NUMBER}-{FILE_TYPE}.md`. FILE_PREFIX combines the
18
18
 
19
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.
20
20
 
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.
21
+ With no filename, `{{TICKET_CMD}} --next` returns FILE_PREFIX instead of FILE_NAME. To name several files at once, repeat `--next <filename>` once per file: the command returns FILE_NAMES, numbered in that order. Add `--new-cycle` to any form when the protocol or user calls for a new cycle.
22
22
 
23
- Neither form reserves a number. Write the file before requesting another filename. With `--json`, the same information uses the same field names.
23
+ No form reserves a number. Write the named file or files before requesting another filename. With `--json`, the same information uses the same field names.
24
24
 
25
25
  Common file types are `spec`, `plan`, `AAD.summary`, `description`, `review`, and `merge.summary`. Use another type when needed.
26
26
 
@@ -207,32 +207,22 @@ Note:
207
207
 
208
208
  ## Phase 5. Writing
209
209
 
210
- Write the plan file(s) according to the determined structure:
210
+ Continue the current cycle. Request the file names with `{{TICKET_CMD}} --next`, then write every file it named before requesting anything else. Append each name to TICKET_DIR to get its path. Never overwrite an existing file.
211
211
 
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.
212
+ **Single plan**: run `{{TICKET_CMD}} --next plan.md`.
213
213
 
214
- **Single Plan**:
214
+ - Plan: `{TICKET_DIR}{CYCLE_LETTER}{FILE_NUMBER}-plan.md`, e.g. `.plans/123/A2-plan.md`
215
+ - Handover: `.plans/123/A2-plan.summary.md`
215
216
 
216
- - **Single plan**: `{TICKET_DIR}{CYCLE_LETTER}{FILE_NUMBER}-plan.md`
217
- - Example: `.plans/123/A2-plan.md`
218
- - Handover: `.plans/123/A2-plan.summary.md`
219
- - No main plan needed
217
+ **Multiple plans**: run one command with a `--next` per file, the main plan first, then the specialized plans in the order the main plan lists them. The command returns all the names, numbered in that order, so the main plan can reference the specialized plans exactly. Example:
220
218
 
221
- **Multiple Plans**:
222
-
223
- - **Main plan**: `{TICKET_DIR}{CYCLE_LETTER}{FILE_NUMBER}-main-plan.md`
224
- - Example: `.plans/123/A2-main-plan.md`
225
- - Handover: `.plans/123/A2-main-plan.summary.md` (written after all specialized plans complete)
226
- - **Specialized plans**: `{TICKET_DIR}{CYCLE_LETTER}{FILE_NUMBER}-plan-{DESCRIPTOR}.md`
227
- - Use a descriptive name as `{DESCRIPTOR}` (e.g., work scope, stack area)
228
- - Example: `.plans/123/A3-plan-api.md`, `.plans/123/A4-plan-ui.md`
229
- - Handovers: `.plans/123/A3-plan-api.summary.md`, etc.
230
-
231
- **Important**:
219
+ ```
220
+ {{TICKET_CMD}} --next main-plan.md --next plan-api.md --next plan-ui.md
221
+ ```
232
222
 
233
- - Use lowercase, hyphenated descriptors for plan names (work scope descriptor)
234
- - When multiple plans are created, the main plan should be written first and have the lowest FILE_NUMBER
235
- - Be careful never to overwrite an existing file
223
+ - Main plan: `{TICKET_DIR}{CYCLE_LETTER}{FILE_NUMBER}-main-plan.md`, e.g. `.plans/123/A2-main-plan.md`
224
+ - Specialized plans: `{TICKET_DIR}{CYCLE_LETTER}{FILE_NUMBER}-plan-{DESCRIPTOR}.md`, e.g. `.plans/123/A3-plan-api.md`, `.plans/123/A4-plan-ui.md`. The descriptor is lowercase and hyphenated and names the work scope or stack area.
225
+ - Handovers: replace the final `.md` with `.summary.md`, e.g. `.plans/123/A3-plan-api.summary.md`. The main plan handover is written after all specialized plans complete.
236
226
 
237
227
  _Important Note: There will be lint errors in the markdown files you write. Ignore them. NEVER FIX LINT ERRORS (FORMATTING ISSUES) IN THE PLANS._
238
228