ai-foreman 1.3.0 → 1.4.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 +13 -1
- package/dist/branch/git.d.ts +2 -0
- package/dist/branch/git.js +18 -1
- package/dist/branch/github.d.ts +3 -1
- package/dist/branch/github.js +32 -0
- package/dist/branch/gitlab.d.ts +15 -0
- package/dist/branch/gitlab.js +290 -0
- package/dist/branch/planner.d.ts +1 -0
- package/dist/branch/planner.js +3 -1
- package/dist/branch/resume.d.ts +2 -0
- package/dist/branch/resume.js +7 -1
- package/dist/branch/runner.d.ts +6 -1
- package/dist/branch/runner.js +155 -20
- package/dist/branch/types.d.ts +10 -2
- package/dist/cli/start.js +135 -7
- package/dist/cli/tickets.d.ts +11 -0
- package/dist/cli/tickets.js +736 -92
- package/dist/foreman.js +2 -2
- package/dist/log.d.ts +1 -1
- package/dist/tickets/commands.d.ts +6 -0
- package/dist/tickets/commands.js +40 -3
- package/dist/tickets/config.d.ts +2 -1
- package/dist/tickets/config.js +20 -4
- package/dist/tickets/importer.d.ts +55 -0
- package/dist/tickets/importer.js +480 -0
- package/dist/tickets/recommendations.d.ts +32 -0
- package/dist/tickets/recommendations.js +183 -0
- package/dist/tickets/renderMarkdown.js +26 -2
- package/dist/tickets/setupConfig.d.ts +77 -0
- package/dist/tickets/setupConfig.js +369 -0
- package/dist/tickets/stateDb.d.ts +17 -0
- package/dist/tickets/stateDb.js +46 -0
- package/dist/tickets/ticketSchema.d.ts +55 -0
- package/dist/tickets/ticketSchema.js +14 -0
- package/dist/tickets/validate.js +2 -2
- package/package.json +2 -2
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;
|
package/dist/log.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** A single structured log record. */
|
|
2
2
|
export interface LogRecord {
|
|
3
3
|
ts: string;
|
|
4
|
-
event: "batch-start" | "batch-end" | "branch-plan" | "branch-start" | "branch-session" | "branch-resume" | "branch-issue" | "branch-complete" | "branch-push" | "github-readiness-failed" | "pr-created" | "pr-existing" | "pr-failed" | "step" | "permission" | "escalation" | "needs_input" | "preflight" | "rafi-plan" | "ticket-populate" | "qa" | "qa-fix" | "error";
|
|
4
|
+
event: "batch-start" | "batch-end" | "branch-plan" | "branch-start" | "branch-session" | "branch-resume" | "branch-issue" | "branch-complete" | "branch-push" | "github-readiness-failed" | "gitlab-readiness-failed" | "pr-created" | "pr-existing" | "pr-failed" | "pr-auto-merge-enabled" | "pr-auto-merge-failed" | "mr-created" | "mr-existing" | "mr-failed" | "mr-auto-merge-enabled" | "mr-auto-merge-failed" | "branch-auto-merge-wait" | "branch-direct-merge" | "step" | "permission" | "escalation" | "needs_input" | "preflight" | "rafi-plan" | "ticket-populate" | "qa" | "qa-fix" | "error";
|
|
5
5
|
[key: string]: unknown;
|
|
6
6
|
}
|
|
7
7
|
/** Append-only JSONL logger. One line per record, plus an echo to the console. */
|
|
@@ -3,6 +3,7 @@ import type { ValidationResult } from "./stateDb.js";
|
|
|
3
3
|
import { type TicketsConfig, resolveTicketPaths } from "./config.js";
|
|
4
4
|
import { StateDb } from "./stateDb.js";
|
|
5
5
|
import { runAllValidation } from "./validate.js";
|
|
6
|
+
import { type ReviewCommandOptions, type ReviewCommandResult } from "./recommendations.js";
|
|
6
7
|
export interface TicketContext {
|
|
7
8
|
config: TicketsConfig;
|
|
8
9
|
projectDir: string;
|
|
@@ -14,6 +15,9 @@ export declare function openContext(projectDir: string): TicketContext;
|
|
|
14
15
|
export interface InitOptions {
|
|
15
16
|
appName?: string;
|
|
16
17
|
timezone?: string;
|
|
18
|
+
implementationLimit?: number;
|
|
19
|
+
viewLimit?: number;
|
|
20
|
+
/** @deprecated Use implementationLimit. */
|
|
17
21
|
queueLimit?: number;
|
|
18
22
|
docsRoot?: string;
|
|
19
23
|
}
|
|
@@ -85,6 +89,8 @@ export interface ValidateResult {
|
|
|
85
89
|
}
|
|
86
90
|
export declare function cmdValidate(projectDir: string): ValidateResult;
|
|
87
91
|
export declare function cmdQueue(projectDir: string, limit?: number): import("./queue.js").QueueRow[];
|
|
92
|
+
export declare function cmdImplementationQueue(projectDir: string): import("./queue.js").QueueRow[];
|
|
88
93
|
export declare function cmdArchive(projectDir: string, _opts: {
|
|
89
94
|
olderThanDays?: number;
|
|
90
95
|
}): void;
|
|
96
|
+
export declare function cmdReview(projectDir: string, opts?: ReviewCommandOptions): ReviewCommandResult;
|
package/dist/tickets/commands.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
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";
|
|
8
8
|
import { renderAndWrite, renderTrackerRules } from "./renderMarkdown.js";
|
|
9
9
|
import { runAllValidation } from "./validate.js";
|
|
10
10
|
import { buildNextQueue } from "./queue.js";
|
|
11
|
+
import { cmdReviewRecommendations } from "./recommendations.js";
|
|
11
12
|
export function openContext(projectDir) {
|
|
12
13
|
const config = loadTicketsConfig(projectDir);
|
|
13
14
|
const paths = resolveTicketPaths(config, projectDir);
|
|
@@ -32,6 +33,9 @@ export function cmdInit(projectDir, opts) {
|
|
|
32
33
|
if (isTicketsInitialized(projectDir)) {
|
|
33
34
|
throw new Error(".tickets/config.yaml already exists — already initialized.");
|
|
34
35
|
}
|
|
36
|
+
if (opts.implementationLimit !== undefined && opts.queueLimit !== undefined) {
|
|
37
|
+
throw new Error("--implementation-limit and deprecated --queue-limit cannot both be set");
|
|
38
|
+
}
|
|
35
39
|
const ticketsDir = join(projectDir, ".tickets");
|
|
36
40
|
const docsRoot = initDocsRoot(projectDir, opts.docsRoot);
|
|
37
41
|
const progressDoc = `${docsRoot}/ticket-progress.md`;
|
|
@@ -46,7 +50,8 @@ export function cmdInit(projectDir, opts) {
|
|
|
46
50
|
mkdirSync(join(projectDir, docsRoot), { recursive: true });
|
|
47
51
|
const config = {
|
|
48
52
|
app_name: opts.appName ?? "My App",
|
|
49
|
-
|
|
53
|
+
implementation_limit: opts.implementationLimit ?? opts.queueLimit ?? DEFAULT_TICKETS_CONFIG.implementationLimit,
|
|
54
|
+
view_limit: opts.viewLimit ?? DEFAULT_TICKETS_CONFIG.viewLimit,
|
|
50
55
|
timezone: opts.timezone ?? "UTC",
|
|
51
56
|
timestamp_format: "iso8601_offset",
|
|
52
57
|
paths: {
|
|
@@ -74,6 +79,14 @@ export function cmdInit(projectDir, opts) {
|
|
|
74
79
|
};
|
|
75
80
|
writeFileSync(join(ticketsDir, "config.yaml"), stringify(config, { lineWidth: 80 }), "utf8");
|
|
76
81
|
writeFileSync(join(ticketsDir, "tickets.yaml"), "tickets: []\n", "utf8");
|
|
82
|
+
writeFileSync(join(ticketsDir, ".gitignore"), [
|
|
83
|
+
"imports/",
|
|
84
|
+
"ticket-state.sqlite",
|
|
85
|
+
"ticket-state.sqlite-shm",
|
|
86
|
+
"ticket-state.sqlite-wal",
|
|
87
|
+
"backups/",
|
|
88
|
+
"",
|
|
89
|
+
].join("\n"), "utf8");
|
|
77
90
|
writeFileSync(join(ticketsDir, "tracker-rules.md"), renderTrackerRules(progressDoc), "utf8");
|
|
78
91
|
const ticketSchema = {
|
|
79
92
|
$schema: "http://json-schema.org/draft-07/schema#",
|
|
@@ -96,6 +109,20 @@ export function cmdInit(projectDir, opts) {
|
|
|
96
109
|
likely_files: { type: "array", items: { type: "string" } },
|
|
97
110
|
rollback: { type: ["string", "null"] },
|
|
98
111
|
notes: { type: ["string", "null"] },
|
|
112
|
+
external_refs: {
|
|
113
|
+
type: "array",
|
|
114
|
+
items: {
|
|
115
|
+
type: "object",
|
|
116
|
+
required: ["provider", "id"],
|
|
117
|
+
additionalProperties: true,
|
|
118
|
+
properties: {
|
|
119
|
+
provider: { type: "string", minLength: 1 },
|
|
120
|
+
id: { type: "string", minLength: 1 },
|
|
121
|
+
key: { type: ["string", "null"] },
|
|
122
|
+
url: { type: ["string", "null"] },
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
},
|
|
99
126
|
},
|
|
100
127
|
};
|
|
101
128
|
writeFileSync(join(ticketsDir, "schema", "tickets.schema.json"), JSON.stringify(ticketSchema, null, 2), "utf8");
|
|
@@ -424,7 +451,13 @@ export function cmdValidate(projectDir) {
|
|
|
424
451
|
export function cmdQueue(projectDir, limit) {
|
|
425
452
|
return withContext(projectDir, (ctx) => {
|
|
426
453
|
const states = ctx.db.getAllStates();
|
|
427
|
-
return buildNextQueue(ctx.tickets, states, limit ?? ctx.config.
|
|
454
|
+
return buildNextQueue(ctx.tickets, states, limit ?? ctx.config.viewLimit);
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
export function cmdImplementationQueue(projectDir) {
|
|
458
|
+
return withContext(projectDir, (ctx) => {
|
|
459
|
+
const states = ctx.db.getAllStates();
|
|
460
|
+
return buildNextQueue(ctx.tickets, states, ctx.config.implementationLimit);
|
|
428
461
|
});
|
|
429
462
|
}
|
|
430
463
|
// ── archive ───────────────────────────────────────────────────────────────────
|
|
@@ -443,3 +476,7 @@ export function cmdArchive(projectDir, _opts) {
|
|
|
443
476
|
renderAfterUpdate(ctx);
|
|
444
477
|
});
|
|
445
478
|
}
|
|
479
|
+
// ── review recommendations ──────────────────────────────────────────────────
|
|
480
|
+
export function cmdReview(projectDir, opts = {}) {
|
|
481
|
+
return cmdReviewRecommendations(projectDir, opts);
|
|
482
|
+
}
|
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`);
|
|
@@ -1 +1,56 @@
|
|
|
1
|
+
import type { TicketDef } from "./ticketSchema.js";
|
|
2
|
+
import type { TicketSourceConfig } from "./setupConfig.js";
|
|
1
3
|
export declare function importFromMarkdown(_progressDocPath: string): never;
|
|
4
|
+
export interface ImportedTicketItem {
|
|
5
|
+
provider: "linear" | "jira";
|
|
6
|
+
providerId: string;
|
|
7
|
+
key?: string | null;
|
|
8
|
+
url?: string | null;
|
|
9
|
+
title: string;
|
|
10
|
+
description?: string | null;
|
|
11
|
+
area?: string | null;
|
|
12
|
+
priority?: string | number | null;
|
|
13
|
+
size?: string | number | null;
|
|
14
|
+
status?: string | null;
|
|
15
|
+
statusCategory?: string | null;
|
|
16
|
+
labels?: string[];
|
|
17
|
+
comments?: ImportedComment[];
|
|
18
|
+
createdAt?: string | null;
|
|
19
|
+
updatedAt?: string | null;
|
|
20
|
+
raw: unknown;
|
|
21
|
+
}
|
|
22
|
+
export interface ImportedComment {
|
|
23
|
+
author?: string | null;
|
|
24
|
+
body: string;
|
|
25
|
+
createdAt?: string | null;
|
|
26
|
+
}
|
|
27
|
+
export interface ExternalImportOptions {
|
|
28
|
+
importCap: number;
|
|
29
|
+
commentLimit: number;
|
|
30
|
+
recommendSplitForXl?: boolean;
|
|
31
|
+
}
|
|
32
|
+
export interface ExternalImportResult {
|
|
33
|
+
provider: "linear" | "jira";
|
|
34
|
+
sourceLabel: string;
|
|
35
|
+
fetched: number;
|
|
36
|
+
created: number;
|
|
37
|
+
updated: number;
|
|
38
|
+
snapshotPath: string;
|
|
39
|
+
}
|
|
40
|
+
export declare function importExternalSources(projectDir: string, sources: Extract<TicketSourceConfig, {
|
|
41
|
+
type: "linear" | "jira";
|
|
42
|
+
}>[], opts: ExternalImportOptions): Promise<ExternalImportResult[]>;
|
|
43
|
+
export declare function validateExternalSourceAccess(source: Extract<TicketSourceConfig, {
|
|
44
|
+
type: "linear" | "jira";
|
|
45
|
+
}>): Promise<void>;
|
|
46
|
+
export declare function fetchLinearIssues(source: Extract<TicketSourceConfig, {
|
|
47
|
+
type: "linear";
|
|
48
|
+
}>, opts: ExternalImportOptions): Promise<ImportedTicketItem[]>;
|
|
49
|
+
export declare function fetchJiraIssues(source: Extract<TicketSourceConfig, {
|
|
50
|
+
type: "jira";
|
|
51
|
+
}>, opts: ExternalImportOptions): Promise<ImportedTicketItem[]>;
|
|
52
|
+
export declare function applyImportedItems(projectDir: string, items: ImportedTicketItem[]): {
|
|
53
|
+
created: number;
|
|
54
|
+
updated: number;
|
|
55
|
+
tickets: TicketDef[];
|
|
56
|
+
};
|