ai-foreman 1.3.0 → 1.3.1
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 +5 -1
- package/dist/cli/tickets.js +20 -7
- package/dist/foreman.js +2 -2
- package/dist/tickets/commands.d.ts +4 -0
- package/dist/tickets/commands.js +13 -3
- package/dist/tickets/config.d.ts +2 -1
- package/dist/tickets/config.js +20 -4
- package/dist/tickets/renderMarkdown.js +3 -2
- package/dist/tickets/validate.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -258,11 +258,15 @@ Init options:
|
|
|
258
258
|
| `-p, --project <dir>` | `./my-project` / `../repo` | current directory | Project to initialize. |
|
|
259
259
|
| `--app-name <name>` | `"My App"` | none | Application name stored in tracker config. |
|
|
260
260
|
| `--timezone <tz>` | `America/Chicago` / `UTC` | `UTC` | IANA timezone. |
|
|
261
|
-
| `--
|
|
261
|
+
| `--implementation-limit <n>` | `250` / `500` / `1000` | `500` | Implementation queue window for generated docs and Foreman selection. |
|
|
262
|
+
| `--view-limit <n>` | `1000` / `20000` | `20000` | Default row limit for the display queue command. |
|
|
263
|
+
| `--queue-limit <n>` | `250` / `500` / `1000` | none | Deprecated alias for `--implementation-limit`. |
|
|
262
264
|
| `--docs-root <dir>` | `docs` / `docs-rafi` | `rafi-config.yaml` docs.root, then `docs` | Root for generated progress/archive docs. |
|
|
263
265
|
|
|
264
266
|
`tickets init` reads `docs.root` from `rafi-config.yaml` when present. `--docs-root` overrides that value. The selected root must be a safe repo-relative directory, and init refuses to overwrite an existing selected `ticket-progress.md`.
|
|
265
267
|
|
|
268
|
+
Init writes `implementation_limit` and `view_limit` to `.tickets/config.yaml`. Existing configs with `queue_limit` still load as an implementation limit, with the old default value `50` upgraded to `500`.
|
|
269
|
+
|
|
266
270
|
This creates:
|
|
267
271
|
|
|
268
272
|
```txt
|
package/dist/cli/tickets.js
CHANGED
|
@@ -8,7 +8,7 @@ import { Log } from "../log.js";
|
|
|
8
8
|
import { Foreman } from "../foreman.js";
|
|
9
9
|
import { printEvents } from "./events.js";
|
|
10
10
|
import { cmdInit, cmdUpdate, cmdComplete, cmdBlock, cmdUnblock, cmdCancel, cmdDiscover, cmdAcceptFutureWork, cmdReorder, cmdRender, cmdValidate, cmdQueue, cmdArchive, } from "../tickets/commands.js";
|
|
11
|
-
import { isTicketsInitialized, loadTicketsConfig } from "../tickets/config.js";
|
|
11
|
+
import { DEFAULT_TICKETS_CONFIG, isTicketsInitialized, loadTicketsConfig } from "../tickets/config.js";
|
|
12
12
|
import { validateDocsRoot } from "../tickets/config.js";
|
|
13
13
|
import { importFromMarkdown } from "../tickets/importer.js";
|
|
14
14
|
import { formatValidationIssues } from "../tickets/validate.js";
|
|
@@ -23,6 +23,10 @@ function cwd(opts) {
|
|
|
23
23
|
function unique(values) {
|
|
24
24
|
return [...new Set(values)];
|
|
25
25
|
}
|
|
26
|
+
function optionWasProvided(command, name) {
|
|
27
|
+
const source = command.getOptionValueSource(name);
|
|
28
|
+
return source !== undefined && source !== "default";
|
|
29
|
+
}
|
|
26
30
|
const TICKET_POPULATE_ROLE = "ticket-maker";
|
|
27
31
|
const RAFI_CONFIG_FILES = ["rafi-config.yaml", "project.yaml"];
|
|
28
32
|
function validateEffort(effort) {
|
|
@@ -134,15 +138,24 @@ export function buildTicketsCommand() {
|
|
|
134
138
|
.option("-p, --project <dir>", "project directory (default: cwd)")
|
|
135
139
|
.option("--app-name <name>", "application name")
|
|
136
140
|
.option("--timezone <tz>", "IANA timezone (e.g. America/Chicago)", "UTC")
|
|
137
|
-
.option("--
|
|
141
|
+
.option("--implementation-limit <n>", "implementation queue window size", String(DEFAULT_TICKETS_CONFIG.implementationLimit))
|
|
142
|
+
.option("--view-limit <n>", "ticket queue display limit", String(DEFAULT_TICKETS_CONFIG.viewLimit))
|
|
143
|
+
.option("--queue-limit <n>", "deprecated alias for --implementation-limit")
|
|
138
144
|
.option("--docs-root <dir>", "repo-relative directory for generated ticket docs")
|
|
139
|
-
.action((opts) => {
|
|
145
|
+
.action((opts, command) => {
|
|
140
146
|
const dir = cwd(opts);
|
|
141
147
|
try {
|
|
148
|
+
const hasImplementationLimit = optionWasProvided(command, "implementationLimit");
|
|
149
|
+
const hasLegacyQueueLimit = optionWasProvided(command, "queueLimit");
|
|
150
|
+
if (hasImplementationLimit && hasLegacyQueueLimit) {
|
|
151
|
+
fail("--implementation-limit and deprecated --queue-limit cannot both be passed");
|
|
152
|
+
}
|
|
142
153
|
cmdInit(dir, {
|
|
143
154
|
appName: opts.appName,
|
|
144
155
|
timezone: opts.timezone,
|
|
145
|
-
|
|
156
|
+
implementationLimit: hasImplementationLimit ? Number(opts.implementationLimit) : undefined,
|
|
157
|
+
viewLimit: optionWasProvided(command, "viewLimit") ? Number(opts.viewLimit) : undefined,
|
|
158
|
+
queueLimit: hasLegacyQueueLimit ? Number(opts.queueLimit) : undefined,
|
|
146
159
|
docsRoot: opts.docsRoot,
|
|
147
160
|
});
|
|
148
161
|
console.log(`foreman tickets: initialized .tickets/ in ${dir}`);
|
|
@@ -489,12 +502,12 @@ export function buildTicketsCommand() {
|
|
|
489
502
|
// ── queue ─────────────────────────────────────────────────────────────────────
|
|
490
503
|
tickets
|
|
491
504
|
.command("queue")
|
|
492
|
-
.description("Print the
|
|
505
|
+
.description("Print the ticket queue to stdout.")
|
|
493
506
|
.option("-p, --project <dir>", "project directory (default: cwd)")
|
|
494
|
-
.option("--limit <n>", "override
|
|
507
|
+
.option("--limit <n>", "override view limit")
|
|
495
508
|
.action((opts) => {
|
|
496
509
|
try {
|
|
497
|
-
const rows = cmdQueue(cwd(opts), opts.limit ? Number(opts.limit) : undefined);
|
|
510
|
+
const rows = cmdQueue(cwd(opts), opts.limit !== undefined ? Number(opts.limit) : undefined);
|
|
498
511
|
if (rows.length === 0) {
|
|
499
512
|
console.log("No remaining tickets.");
|
|
500
513
|
}
|
package/dist/foreman.js
CHANGED
|
@@ -2,7 +2,7 @@ import { select, isCancel } from "@clack/prompts";
|
|
|
2
2
|
import { MARKER_SPEC, QA_MARKER_SPEC } from "./markers.js";
|
|
3
3
|
import { fireNotification } from "./notify.js";
|
|
4
4
|
import { isTicketsInitialized } from "./tickets/config.js";
|
|
5
|
-
import { cmdUpdate, cmdComplete, cmdBlock,
|
|
5
|
+
import { cmdUpdate, cmdComplete, cmdBlock, cmdImplementationQueue } from "./tickets/commands.js";
|
|
6
6
|
/** Phrases that suggest the builder ended its turn by asking the human. */
|
|
7
7
|
const QUESTION_HINTS = [
|
|
8
8
|
"should i",
|
|
@@ -324,7 +324,7 @@ export class Foreman {
|
|
|
324
324
|
let pendingTicketId;
|
|
325
325
|
if (this.ticketsEnabled && this.projectDir) {
|
|
326
326
|
try {
|
|
327
|
-
const queue =
|
|
327
|
+
const queue = cmdImplementationQueue(this.projectDir);
|
|
328
328
|
const next = queue.find((r) => r.status === "next");
|
|
329
329
|
if (next) {
|
|
330
330
|
pendingTicketId = next.ticket;
|
|
@@ -14,6 +14,9 @@ export declare function openContext(projectDir: string): TicketContext;
|
|
|
14
14
|
export interface InitOptions {
|
|
15
15
|
appName?: string;
|
|
16
16
|
timezone?: string;
|
|
17
|
+
implementationLimit?: number;
|
|
18
|
+
viewLimit?: number;
|
|
19
|
+
/** @deprecated Use implementationLimit. */
|
|
17
20
|
queueLimit?: number;
|
|
18
21
|
docsRoot?: string;
|
|
19
22
|
}
|
|
@@ -85,6 +88,7 @@ export interface ValidateResult {
|
|
|
85
88
|
}
|
|
86
89
|
export declare function cmdValidate(projectDir: string): ValidateResult;
|
|
87
90
|
export declare function cmdQueue(projectDir: string, limit?: number): import("./queue.js").QueueRow[];
|
|
91
|
+
export declare function cmdImplementationQueue(projectDir: string): import("./queue.js").QueueRow[];
|
|
88
92
|
export declare function cmdArchive(projectDir: string, _opts: {
|
|
89
93
|
olderThanDays?: number;
|
|
90
94
|
}): void;
|
package/dist/tickets/commands.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { stringify } from "yaml";
|
|
4
|
-
import { initDocsRoot, loadTicketsConfig, pathExistsOrSymlink, resolveTicketPaths, isTicketsInitialized, } from "./config.js";
|
|
4
|
+
import { DEFAULT_TICKETS_CONFIG, initDocsRoot, loadTicketsConfig, pathExistsOrSymlink, resolveTicketPaths, isTicketsInitialized, } from "./config.js";
|
|
5
5
|
import { loadTickets, saveTickets } from "./ticketLoader.js";
|
|
6
6
|
import { StateDb } from "./stateDb.js";
|
|
7
7
|
import { nowTimestamp, logEvent } from "./events.js";
|
|
@@ -32,6 +32,9 @@ export function cmdInit(projectDir, opts) {
|
|
|
32
32
|
if (isTicketsInitialized(projectDir)) {
|
|
33
33
|
throw new Error(".tickets/config.yaml already exists — already initialized.");
|
|
34
34
|
}
|
|
35
|
+
if (opts.implementationLimit !== undefined && opts.queueLimit !== undefined) {
|
|
36
|
+
throw new Error("--implementation-limit and deprecated --queue-limit cannot both be set");
|
|
37
|
+
}
|
|
35
38
|
const ticketsDir = join(projectDir, ".tickets");
|
|
36
39
|
const docsRoot = initDocsRoot(projectDir, opts.docsRoot);
|
|
37
40
|
const progressDoc = `${docsRoot}/ticket-progress.md`;
|
|
@@ -46,7 +49,8 @@ export function cmdInit(projectDir, opts) {
|
|
|
46
49
|
mkdirSync(join(projectDir, docsRoot), { recursive: true });
|
|
47
50
|
const config = {
|
|
48
51
|
app_name: opts.appName ?? "My App",
|
|
49
|
-
|
|
52
|
+
implementation_limit: opts.implementationLimit ?? opts.queueLimit ?? DEFAULT_TICKETS_CONFIG.implementationLimit,
|
|
53
|
+
view_limit: opts.viewLimit ?? DEFAULT_TICKETS_CONFIG.viewLimit,
|
|
50
54
|
timezone: opts.timezone ?? "UTC",
|
|
51
55
|
timestamp_format: "iso8601_offset",
|
|
52
56
|
paths: {
|
|
@@ -424,7 +428,13 @@ export function cmdValidate(projectDir) {
|
|
|
424
428
|
export function cmdQueue(projectDir, limit) {
|
|
425
429
|
return withContext(projectDir, (ctx) => {
|
|
426
430
|
const states = ctx.db.getAllStates();
|
|
427
|
-
return buildNextQueue(ctx.tickets, states, limit ?? ctx.config.
|
|
431
|
+
return buildNextQueue(ctx.tickets, states, limit ?? ctx.config.viewLimit);
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
export function cmdImplementationQueue(projectDir) {
|
|
435
|
+
return withContext(projectDir, (ctx) => {
|
|
436
|
+
const states = ctx.db.getAllStates();
|
|
437
|
+
return buildNextQueue(ctx.tickets, states, ctx.config.implementationLimit);
|
|
428
438
|
});
|
|
429
439
|
}
|
|
430
440
|
// ── archive ───────────────────────────────────────────────────────────────────
|
package/dist/tickets/config.d.ts
CHANGED
package/dist/tickets/config.js
CHANGED
|
@@ -3,7 +3,8 @@ import { dirname, isAbsolute, join, normalize, relative, resolve } from "node:pa
|
|
|
3
3
|
import { parse } from "yaml";
|
|
4
4
|
export const DEFAULT_TICKETS_CONFIG = {
|
|
5
5
|
appName: "My App",
|
|
6
|
-
|
|
6
|
+
implementationLimit: 500,
|
|
7
|
+
viewLimit: 20_000,
|
|
7
8
|
timezone: "UTC",
|
|
8
9
|
paths: {
|
|
9
10
|
tickets: ".tickets/tickets.yaml",
|
|
@@ -126,7 +127,8 @@ export function loadTicketsConfig(projectDir) {
|
|
|
126
127
|
assertPlainObject(raw.behavior, "behavior", configPath);
|
|
127
128
|
const config = {
|
|
128
129
|
appName: raw.app_name ?? DEFAULT_TICKETS_CONFIG.appName,
|
|
129
|
-
|
|
130
|
+
implementationLimit: resolveImplementationLimit(raw),
|
|
131
|
+
viewLimit: raw.view_limit ?? DEFAULT_TICKETS_CONFIG.viewLimit,
|
|
130
132
|
timezone: raw.timezone ?? DEFAULT_TICKETS_CONFIG.timezone,
|
|
131
133
|
paths: {
|
|
132
134
|
...DEFAULT_TICKETS_CONFIG.paths,
|
|
@@ -144,6 +146,17 @@ export function loadTicketsConfig(projectDir) {
|
|
|
144
146
|
validateTicketsConfig(config, configPath);
|
|
145
147
|
return config;
|
|
146
148
|
}
|
|
149
|
+
function resolveImplementationLimit(raw) {
|
|
150
|
+
if (raw.implementation_limit !== undefined) {
|
|
151
|
+
return raw.implementation_limit;
|
|
152
|
+
}
|
|
153
|
+
if (raw.queue_limit !== undefined) {
|
|
154
|
+
return raw.queue_limit === 50
|
|
155
|
+
? DEFAULT_TICKETS_CONFIG.implementationLimit
|
|
156
|
+
: raw.queue_limit;
|
|
157
|
+
}
|
|
158
|
+
return DEFAULT_TICKETS_CONFIG.implementationLimit;
|
|
159
|
+
}
|
|
147
160
|
function assertPlainObject(value, name, configPath) {
|
|
148
161
|
if (value !== undefined && (!value || typeof value !== "object" || Array.isArray(value))) {
|
|
149
162
|
throw new Error(`${configPath}: ${name} must be an object`);
|
|
@@ -153,8 +166,11 @@ function validateTicketsConfig(config, configPath) {
|
|
|
153
166
|
if (!config.appName || typeof config.appName !== "string") {
|
|
154
167
|
throw new Error(`${configPath}: app_name must be a non-empty string`);
|
|
155
168
|
}
|
|
156
|
-
if (!Number.isInteger(config.
|
|
157
|
-
throw new Error(`${configPath}:
|
|
169
|
+
if (!Number.isInteger(config.implementationLimit) || config.implementationLimit < 1) {
|
|
170
|
+
throw new Error(`${configPath}: implementation_limit must be a positive integer`);
|
|
171
|
+
}
|
|
172
|
+
if (!Number.isInteger(config.viewLimit) || config.viewLimit < 1) {
|
|
173
|
+
throw new Error(`${configPath}: view_limit must be a positive integer`);
|
|
158
174
|
}
|
|
159
175
|
if (!config.timezone || typeof config.timezone !== "string") {
|
|
160
176
|
throw new Error(`${configPath}: timezone must be a non-empty IANA timezone string`);
|
|
@@ -59,7 +59,7 @@ export const DEFAULT_TRACKER_RULES = renderTrackerRules();
|
|
|
59
59
|
export function renderProgressDoc(input) {
|
|
60
60
|
const { config, projectDir, ticketDefs, states, db } = input;
|
|
61
61
|
const now = input.now ?? DateTime.now().setZone(config.timezone).toISO();
|
|
62
|
-
const queueRows = buildNextQueue(ticketDefs, states, config.
|
|
62
|
+
const queueRows = buildNextQueue(ticketDefs, states, config.implementationLimit);
|
|
63
63
|
const activeRows = buildActiveStatusRows(queueRows, ticketDefs, states);
|
|
64
64
|
const futureWork = db.getFutureWork();
|
|
65
65
|
const recentEvents = db.getRecentEvents(config.rendering.maxWorkLogRows);
|
|
@@ -82,7 +82,8 @@ export function renderProgressDoc(input) {
|
|
|
82
82
|
lines.push("## Tracker Metadata");
|
|
83
83
|
lines.push(section("TRACKER_METADATA", table(["Field", "Value"], [
|
|
84
84
|
["App", config.appName],
|
|
85
|
-
["
|
|
85
|
+
["Implementation Limit", String(config.implementationLimit)],
|
|
86
|
+
["View Limit", String(config.viewLimit)],
|
|
86
87
|
["Timezone", config.timezone],
|
|
87
88
|
["Generated At", now],
|
|
88
89
|
["Remaining Tickets", String(queueRows.length > 0 ? "≥" + queueRows.length : "0")],
|
package/dist/tickets/validate.js
CHANGED
|
@@ -25,12 +25,12 @@ export function runAllValidation(config, projectDir, ticketDefs, states, db) {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
// Pass 3: queue validation
|
|
28
|
-
const queueRows = buildNextQueue(ticketDefs, states, config.
|
|
28
|
+
const queueRows = buildNextQueue(ticketDefs, states, config.implementationLimit);
|
|
29
29
|
const remaining = ticketDefs.filter((t) => {
|
|
30
30
|
const s = states.get(t.id);
|
|
31
31
|
return s?.status !== "done" && s?.status !== "canceled";
|
|
32
32
|
});
|
|
33
|
-
const expectedLen = Math.min(config.
|
|
33
|
+
const expectedLen = Math.min(config.implementationLimit, remaining.length);
|
|
34
34
|
if (queueRows.length !== expectedLen) {
|
|
35
35
|
issues.push({ pass: 3, severity: "error", message: `[queue] expected ${expectedLen} rows, got ${queueRows.length}` });
|
|
36
36
|
}
|
package/package.json
CHANGED