@clemrig/factory 0.3.0 → 0.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.
Files changed (90) hide show
  1. package/README.md +111 -21
  2. package/dist/app/board.d.ts +7 -0
  3. package/dist/app/board.js +47 -0
  4. package/dist/app/board.js.map +1 -0
  5. package/dist/app/define.d.ts +27 -0
  6. package/dist/app/define.js +68 -0
  7. package/dist/app/define.js.map +1 -0
  8. package/dist/app/doctor.js +5 -4
  9. package/dist/app/doctor.js.map +1 -1
  10. package/dist/app/factory.js +10 -2
  11. package/dist/app/factory.js.map +1 -1
  12. package/dist/app/run.js +1 -1
  13. package/dist/app/run.js.map +1 -1
  14. package/dist/app/workflows/implement.d.ts +8 -0
  15. package/dist/app/workflows/implement.js +268 -0
  16. package/dist/app/workflows/implement.js.map +1 -0
  17. package/dist/app/workflows/issue-save.d.ts +2 -0
  18. package/dist/app/workflows/issue-save.js +35 -0
  19. package/dist/app/workflows/issue-save.js.map +1 -0
  20. package/dist/cli/application.js +1 -1
  21. package/dist/cli/application.js.map +1 -1
  22. package/dist/cli/help.d.ts +1 -1
  23. package/dist/cli/help.js +5 -3
  24. package/dist/cli/help.js.map +1 -1
  25. package/dist/cli/init.d.ts +2 -2
  26. package/dist/cli/init.js +4 -4
  27. package/dist/cli/init.js.map +1 -1
  28. package/dist/cli/ui.d.ts +5 -2
  29. package/dist/cli/ui.js +20 -7
  30. package/dist/cli/ui.js.map +1 -1
  31. package/dist/domain/board.d.ts +65 -0
  32. package/dist/domain/board.js +95 -0
  33. package/dist/domain/board.js.map +1 -0
  34. package/dist/domain/definition.d.ts +15 -0
  35. package/dist/domain/definition.js +28 -0
  36. package/dist/domain/definition.js.map +1 -0
  37. package/dist/domain/delivery.d.ts +9 -0
  38. package/dist/domain/delivery.js +10 -0
  39. package/dist/domain/delivery.js.map +1 -0
  40. package/dist/domain/issue.d.ts +11 -0
  41. package/dist/domain/issue.js +22 -0
  42. package/dist/domain/issue.js.map +1 -0
  43. package/dist/domain/parsed.d.ts +8 -0
  44. package/dist/domain/parsed.js +2 -0
  45. package/dist/domain/parsed.js.map +1 -0
  46. package/dist/domain/plan.d.ts +24 -0
  47. package/dist/domain/plan.js +49 -0
  48. package/dist/domain/plan.js.map +1 -0
  49. package/dist/domain/pull-request.d.ts +17 -0
  50. package/dist/domain/pull-request.js +32 -0
  51. package/dist/domain/pull-request.js.map +1 -0
  52. package/dist/domain/review.d.ts +36 -0
  53. package/dist/domain/review.js +45 -0
  54. package/dist/domain/review.js.map +1 -0
  55. package/dist/domain/spec.d.ts +18 -0
  56. package/dist/domain/spec.js +118 -0
  57. package/dist/domain/spec.js.map +1 -0
  58. package/dist/domain/verification.d.ts +16 -0
  59. package/dist/domain/verification.js +11 -0
  60. package/dist/domain/verification.js.map +1 -0
  61. package/dist/index.d.ts +8 -4
  62. package/dist/index.js +5 -3
  63. package/dist/index.js.map +1 -1
  64. package/dist/system/checks.d.ts +32 -0
  65. package/dist/system/checks.js +49 -0
  66. package/dist/system/checks.js.map +1 -0
  67. package/dist/system/factory.d.ts +7 -5
  68. package/dist/system/factory.js +19 -19
  69. package/dist/system/factory.js.map +1 -1
  70. package/dist/system/git.d.ts +25 -0
  71. package/dist/system/git.js +73 -0
  72. package/dist/system/git.js.map +1 -0
  73. package/dist/system/github.d.ts +8 -0
  74. package/dist/system/github.js +52 -0
  75. package/dist/system/github.js.map +1 -0
  76. package/dist/system/issues.d.ts +24 -0
  77. package/dist/system/issues.js +66 -0
  78. package/dist/system/issues.js.map +1 -0
  79. package/dist/system/lock.d.ts +8 -0
  80. package/dist/system/lock.js +35 -0
  81. package/dist/system/lock.js.map +1 -0
  82. package/dist/system/templates.d.ts +2 -1
  83. package/dist/system/templates.js +69 -3
  84. package/dist/system/templates.js.map +1 -1
  85. package/package.json +6 -4
  86. package/prompts/implement/implement.md +21 -0
  87. package/prompts/implement/plan.md +20 -0
  88. package/prompts/implement/review.md +17 -0
  89. package/templates/init/factory.ts +37 -2
  90. package/templates/skills/issue/SKILL.md +82 -0
@@ -0,0 +1,95 @@
1
+ import { summarizeRun } from "@clemrig/flow";
2
+ import { DELIVERY, pullRequestOf } from "./delivery.js";
3
+ /** The columns, in the order an issue goes through them. */
4
+ export const columns = [
5
+ "Backlog",
6
+ "Specifying",
7
+ "Executing",
8
+ "Blocked",
9
+ "To Approve",
10
+ "Done",
11
+ ];
12
+ /** No status is stored: the column follows from the issue's files, the main branch and its last delivery. */
13
+ export function columnOf(issue, lastDelivery) {
14
+ if (issue.delivered)
15
+ return "Done";
16
+ if (lastDelivery?.status === "running")
17
+ return "Executing";
18
+ if (lastDelivery?.status === "failed")
19
+ return "Blocked";
20
+ if (lastDelivery)
21
+ return "To Approve";
22
+ return issue.hasSpec ? "Specifying" : "Backlog";
23
+ }
24
+ /** How far a run went through its workflow's steps; a correction brings its step back. */
25
+ export function stepsOf(steps, run) {
26
+ const at = steps.findIndex(({ stages }) => stages.includes(run.stage));
27
+ return steps.map(({ name }, index) => ({
28
+ name,
29
+ state: run.status === "completed" || index < at
30
+ ? "done"
31
+ : index > at || at < 0
32
+ ? "pending"
33
+ : run.status === "running"
34
+ ? "current"
35
+ : "failed",
36
+ }));
37
+ }
38
+ function progressOf(run, steps) {
39
+ const { durationMs, costUsd, partial } = summarizeRun(run);
40
+ return {
41
+ id: run.id,
42
+ workflow: run.workflow,
43
+ status: run.status,
44
+ stage: run.stage,
45
+ steps: stepsOf(steps, run),
46
+ durationMs,
47
+ costUsd,
48
+ partial,
49
+ };
50
+ }
51
+ /**
52
+ * The Kanban: each issue in its column, the most recently active first, with
53
+ * the last run of each shown workflow. `runs` come in creation order.
54
+ */
55
+ export function boardOf(issues, runs, shown, problems) {
56
+ const placed = issues.map((issue) => {
57
+ const own = runs.filter(({ input }) => input.issue === issue.slug);
58
+ const last = (workflow) => own.findLast((run) => run.workflow === workflow);
59
+ // The last delivery sets the column. A new delivery updates the same pull
60
+ // request: the newest one recorded stays shown.
61
+ const delivery = last(DELIVERY);
62
+ const pullRequest = own.map(pullRequestOf).findLast(Boolean);
63
+ const cardRuns = shown
64
+ .flatMap(({ name, steps }) => {
65
+ const run = last(name);
66
+ return run ? [{ run, steps }] : [];
67
+ })
68
+ .sort((a, b) => b.run.createdAt.localeCompare(a.run.createdAt))
69
+ .map(({ run, steps }) => progressOf(run, steps));
70
+ const activity = [issue.changedAt, ...own.map((run) => run.updatedAt)]
71
+ .sort()
72
+ .at(-1);
73
+ return {
74
+ column: columnOf(issue, delivery),
75
+ activity: activity ?? issue.changedAt,
76
+ card: {
77
+ slug: issue.slug,
78
+ title: issue.title,
79
+ ...(pullRequest ? { pullRequest } : {}),
80
+ runs: cardRuns,
81
+ },
82
+ };
83
+ });
84
+ return {
85
+ columns: columns.map((name) => ({
86
+ name,
87
+ cards: placed
88
+ .filter(({ column }) => column === name)
89
+ .sort((a, b) => b.activity.localeCompare(a.activity))
90
+ .map(({ card }) => card),
91
+ })),
92
+ problems,
93
+ };
94
+ }
95
+ //# sourceMappingURL=board.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"board.js","sourceRoot":"","sources":["../../src/domain/board.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import { type AgentProfile, type Workflow } from "@clemrig/flow";
2
+ import type { VerifyCommand } from "./verification.js";
3
+ /** What a repository decides: its checks, its budget per run, its agents and its own workflows. */
4
+ export interface FactoryDefinition {
5
+ verify: VerifyCommand[];
6
+ maxCostUsd: number;
7
+ profiles: {
8
+ plan: AgentProfile;
9
+ implement: AgentProfile;
10
+ review: AgentProfile;
11
+ };
12
+ /** The repository's own workflows, beside the factory's. */
13
+ workflows?: Workflow[];
14
+ }
15
+ export declare function definitionProblems(value: unknown): string[];
@@ -0,0 +1,28 @@
1
+ import { z } from "@clemrig/flow";
2
+ // Profiles are validated by defineWorkflow: each declares the tools its agent may use.
3
+ const profile = z.looseObject({});
4
+ const definition = z.strictObject({
5
+ verify: z
6
+ .array(z.strictObject({
7
+ name: z.string().min(1),
8
+ /** The executable, then its arguments; run without a shell. */
9
+ command: z.tuple([z.string().min(1)], z.string()),
10
+ timeoutMs: z.number().int().positive(),
11
+ }))
12
+ .min(1),
13
+ maxCostUsd: z.number().positive(),
14
+ profiles: z.strictObject({
15
+ plan: profile,
16
+ implement: profile,
17
+ review: profile,
18
+ }),
19
+ // Each workflow is checked where the factory loads them.
20
+ workflows: z.array(z.unknown()).optional(),
21
+ });
22
+ export function definitionProblems(value) {
23
+ const parsed = definition.safeParse(value);
24
+ return parsed.success
25
+ ? []
26
+ : parsed.error.issues.map(({ path, message }) => `${path.join(".")}: ${message}`);
27
+ }
28
+ //# sourceMappingURL=definition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"definition.js","sourceRoot":"","sources":["../../src/domain/definition.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoC,CAAC,EAAE,MAAM,eAAe,CAAC;AAgBpE,uFAAuF;AACvF,MAAM,OAAO,GAAG,CAAC,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;AAElC,MAAM,UAAU,GAAG,CAAC,CAAC,YAAY,CAAC;IAChC,MAAM,EAAE,CAAC;SACN,KAAK,CACJ,CAAC,CAAC,YAAY,CAAC;QACb,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QACvB,+DAA+D;QAC/D,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC;QACjD,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;KACvC,CAAC,CACH;SACA,GAAG,CAAC,CAAC,CAAC;IACT,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACjC,QAAQ,EAAE,CAAC,CAAC,YAAY,CAAC;QACvB,IAAI,EAAE,OAAO;QACb,SAAS,EAAE,OAAO;QAClB,MAAM,EAAE,OAAO;KAChB,CAAC;IACF,yDAAyD;IACzD,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,EAAE;CAC3C,CAAC,CAAC;AAEH,MAAM,UAAU,kBAAkB,CAAC,KAAc;IAC/C,MAAM,MAAM,GAAG,UAAU,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC3C,OAAO,MAAM,CAAC,OAAO;QACnB,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CACrB,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,OAAO,EAAE,CACvD,CAAC;AACR,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { RunState } from "@clemrig/flow";
2
+ /** The workflow that delivers an issue: its last run sets the issue's column. */
3
+ export declare const DELIVERY = "implement";
4
+ /** What a delivery records once it opened its pull request. */
5
+ export interface Delivered {
6
+ pullRequest: string;
7
+ }
8
+ /** The pull request a delivery run recorded, if it got that far. */
9
+ export declare function pullRequestOf(run: Pick<RunState, "workflow" | "data">): string | undefined;
@@ -0,0 +1,10 @@
1
+ /** The workflow that delivers an issue: its last run sets the issue's column. */
2
+ export const DELIVERY = "implement";
3
+ /** The pull request a delivery run recorded, if it got that far. */
4
+ export function pullRequestOf(run) {
5
+ if (run.workflow !== DELIVERY)
6
+ return undefined;
7
+ const { pullRequest } = run.data;
8
+ return typeof pullRequest === "string" ? pullRequest : undefined;
9
+ }
10
+ //# sourceMappingURL=delivery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delivery.js","sourceRoot":"","sources":["../../src/domain/delivery.ts"],"names":[],"mappings":"AAEA,iFAAiF;AACjF,MAAM,CAAC,MAAM,QAAQ,GAAG,WAAW,CAAC;AAOpC,oEAAoE;AACpE,MAAM,UAAU,aAAa,CAC3B,GAAwC;IAExC,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAChD,MAAM,EAAE,WAAW,EAAE,GAAG,GAAG,CAAC,IAA0B,CAAC;IACvD,OAAO,OAAO,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC;AACnE,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { Parsed } from "./parsed.js";
2
+ /** An issue's identity: its folder, branch and run input. */
3
+ export declare function isSlug(value: string): boolean;
4
+ export interface Issue {
5
+ title: string;
6
+ description: string;
7
+ }
8
+ /** The `# ` title on the first line of issue.md; empty without one. */
9
+ export declare function issueTitle(text: string): string;
10
+ /** Reads issue.md: a `# ` title, then a free description. */
11
+ export declare function parseIssue(text: string): Parsed<Issue>;
@@ -0,0 +1,22 @@
1
+ /** An issue's identity: its folder, branch and run input. */
2
+ export function isSlug(value) {
3
+ return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value);
4
+ }
5
+ /** The `# ` title on the first line of issue.md; empty without one. */
6
+ export function issueTitle(text) {
7
+ const [first = ""] = text.trim().split(/\r?\n/);
8
+ return /^# (.*)$/.exec(first)?.[1]?.trim() ?? "";
9
+ }
10
+ /** Reads issue.md: a `# ` title, then a free description. */
11
+ export function parseIssue(text) {
12
+ const title = issueTitle(text);
13
+ const description = text.trim().split(/\r?\n/).slice(1).join("\n").trim();
14
+ const problems = [
15
+ ...(title ? [] : ["The first line must be a title: # <Title>"]),
16
+ ...(description ? [] : ["The description is empty"]),
17
+ ];
18
+ return problems.length > 0
19
+ ? { ok: false, problems }
20
+ : { ok: true, value: { title, description } };
21
+ }
22
+ //# sourceMappingURL=issue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"issue.js","sourceRoot":"","sources":["../../src/domain/issue.ts"],"names":[],"mappings":"AAEA,6DAA6D;AAC7D,MAAM,UAAU,MAAM,CAAC,KAAa;IAClC,OAAO,4BAA4B,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAClD,CAAC;AAOD,uEAAuE;AACvE,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,MAAM,CAAC,KAAK,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAChD,OAAO,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AACnD,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;IAC1E,MAAM,QAAQ,GAAG;QACf,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,2CAA2C,CAAC,CAAC;QAC/D,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,0BAA0B,CAAC,CAAC;KACrD,CAAC;IACF,OAAO,QAAQ,CAAC,MAAM,GAAG,CAAC;QACxB,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE;QACzB,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,CAAC;AAClD,CAAC"}
@@ -0,0 +1,8 @@
1
+ /** A parsed document, or every problem found in it. */
2
+ export type Parsed<T> = {
3
+ ok: true;
4
+ value: T;
5
+ } | {
6
+ ok: false;
7
+ problems: string[];
8
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=parsed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parsed.js","sourceRoot":"","sources":["../../src/domain/parsed.ts"],"names":[],"mappings":""}
@@ -0,0 +1,24 @@
1
+ import { z } from "@clemrig/flow";
2
+ import { type Criterion } from "./spec.js";
3
+ /** The planning agent's response. */
4
+ export declare const planOutput: z.ZodObject<{
5
+ summary: z.ZodString;
6
+ commit: z.ZodString;
7
+ steps: z.ZodArray<z.ZodObject<{
8
+ title: z.ZodString;
9
+ description: z.ZodString;
10
+ criteria: z.ZodArray<z.ZodString>;
11
+ files: z.ZodArray<z.ZodString>;
12
+ tests: z.ZodString;
13
+ }, z.core.$strict>>;
14
+ risks: z.ZodArray<z.ZodString>;
15
+ }, z.core.$strict>;
16
+ export type Plan = z.output<typeof planOutput>;
17
+ /** Every criterion of the spec is covered by at least one step. */
18
+ export declare function planProblem(plan: Plan, criteria: Criterion[]): string | undefined;
19
+ export declare function renderPlan(plan: Plan, source: {
20
+ title: string;
21
+ specPath: string;
22
+ baseCommit: string;
23
+ }): string;
24
+ export declare function planCommitMessage(slug: string): string;
@@ -0,0 +1,49 @@
1
+ import { z } from "@clemrig/flow";
2
+ import { citationProblem } from "./spec.js";
3
+ const text = z.string().trim().min(1);
4
+ /** The planning agent's response. */
5
+ export const planOutput = z.strictObject({
6
+ summary: text,
7
+ /** Subject of the implementation's commit, also the PR title. */
8
+ commit: z
9
+ .string()
10
+ .max(72)
11
+ // One line: it is the commit's subject and the PR's title.
12
+ .regex(/^(feat|fix|refactor|perf|test|docs|chore)(\([a-z0-9-]+\))?: \S[^\r\n]*$/),
13
+ steps: z
14
+ .array(z.strictObject({
15
+ title: text,
16
+ description: text,
17
+ criteria: z.array(text).min(1),
18
+ files: z.array(text).min(1),
19
+ tests: text,
20
+ }))
21
+ .min(1),
22
+ risks: z.array(text),
23
+ });
24
+ /** Every criterion of the spec is covered by at least one step. */
25
+ export function planProblem(plan, criteria) {
26
+ return citationProblem(plan.steps.flatMap((step) => step.criteria), criteria);
27
+ }
28
+ export function renderPlan(plan, source) {
29
+ const steps = plan.steps.map((step, index) => [
30
+ `### ${index + 1}. ${step.title}`,
31
+ `Criteria: ${step.criteria.join(", ")}\nFiles: ${step.files.map((file) => `\`${file}\``).join(", ")}`,
32
+ step.description,
33
+ `Tests: ${step.tests}`,
34
+ ].join("\n\n"));
35
+ const risks = plan.risks.length > 0
36
+ ? plan.risks.map((risk) => `- ${risk}`).join("\n")
37
+ : "None.";
38
+ return `${[
39
+ `# Plan: ${source.title}`,
40
+ `Spec: \`${source.specPath}\` at ${source.baseCommit.slice(0, 7)}`,
41
+ `## Summary\n\n${plan.summary}`,
42
+ `## Steps\n\n${steps.join("\n\n")}`,
43
+ `## Risks\n\n${risks}`,
44
+ ].join("\n\n")}\n`;
45
+ }
46
+ export function planCommitMessage(slug) {
47
+ return `docs: plan ${slug}`;
48
+ }
49
+ //# sourceMappingURL=plan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.js","sourceRoot":"","sources":["../../src/domain/plan.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,eAAe,CAAC;AAClC,OAAO,EAAkB,eAAe,EAAE,MAAM,WAAW,CAAC;AAE5D,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AAEtC,qCAAqC;AACrC,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC,YAAY,CAAC;IACvC,OAAO,EAAE,IAAI;IACb,iEAAiE;IACjE,MAAM,EAAE,CAAC;SACN,MAAM,EAAE;SACR,GAAG,CAAC,EAAE,CAAC;QACR,2DAA2D;SAC1D,KAAK,CACJ,yEAAyE,CAC1E;IACH,KAAK,EAAE,CAAC;SACL,KAAK,CACJ,CAAC,CAAC,YAAY,CAAC;QACb,KAAK,EAAE,IAAI;QACX,WAAW,EAAE,IAAI;QACjB,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC9B,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC3B,KAAK,EAAE,IAAI;KACZ,CAAC,CACH;SACA,GAAG,CAAC,CAAC,CAAC;IACT,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC;CACrB,CAAC,CAAC;AAGH,mEAAmE;AACnE,MAAM,UAAU,WAAW,CACzB,IAAU,EACV,QAAqB;IAErB,OAAO,eAAe,CACpB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,EAC3C,QAAQ,CACT,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,UAAU,CACxB,IAAU,EACV,MAA+D;IAE/D,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAC3C;QACE,OAAO,KAAK,GAAG,CAAC,KAAK,IAAI,CAAC,KAAK,EAAE;QACjC,aAAa,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QACrG,IAAI,CAAC,WAAW;QAChB,UAAU,IAAI,CAAC,KAAK,EAAE;KACvB,CAAC,IAAI,CAAC,MAAM,CAAC,CACf,CAAC;IACF,MAAM,KAAK,GACT,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;QACnB,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;QAClD,CAAC,CAAC,OAAO,CAAC;IACd,OAAO,GAAG;QACR,WAAW,MAAM,CAAC,KAAK,EAAE;QACzB,WAAW,MAAM,CAAC,QAAQ,SAAS,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE;QAClE,iBAAiB,IAAI,CAAC,OAAO,EAAE;QAC/B,eAAe,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE;QACnC,eAAe,KAAK,EAAE;KACvB,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,OAAO,cAAc,IAAI,EAAE,CAAC;AAC9B,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { Implementation, Review } from "./review.js";
2
+ import type { Criterion } from "./spec.js";
3
+ import { type CheckResult } from "./verification.js";
4
+ export interface PullRequestFacts {
5
+ criteria: Criterion[];
6
+ implementation: Implementation;
7
+ review: Review;
8
+ checks: CheckResult[];
9
+ /** Corrections the review asked for before approving. */
10
+ reviewRounds: number;
11
+ specPath: string;
12
+ planPath: string;
13
+ baseCommit: string;
14
+ runId: string;
15
+ }
16
+ /** Everything needed to approve the change without opening the run. */
17
+ export declare function pullRequestBody(facts: PullRequestFacts): string;
@@ -0,0 +1,32 @@
1
+ import { seconds } from "./verification.js";
2
+ /** Agent text inside a table cell: one line, no column separator. */
3
+ function cell(text) {
4
+ return text.replace(/\s*\n\s*/g, " ").replaceAll("|", "\\|");
5
+ }
6
+ /** Everything needed to approve the change without opening the run. */
7
+ export function pullRequestBody(facts) {
8
+ const { implementation, review } = facts;
9
+ const criteria = facts.criteria.map(({ id, text }) => {
10
+ const judged = review.criteria.find((criterion) => criterion.id === id);
11
+ const status = judged?.status === "met" ? "Met" : "Not met";
12
+ return `| ${cell(`${id}: ${text}`)} | ${status} | ${cell(judged?.evidence ?? "")} |`;
13
+ });
14
+ const checks = facts.checks.map(({ name, command, durationMs }) => `- ${name}: \`${command.join(" ")}\` passed in ${seconds(durationMs)}`);
15
+ const rounds = facts.reviewRounds === 0
16
+ ? "Approved at the first review."
17
+ : `Approved after ${facts.reviewRounds} round${facts.reviewRounds === 1 ? "" : "s"} of changes.`;
18
+ const minor = review.findings
19
+ .filter(({ severity }) => severity === "minor")
20
+ .map(({ file, summary }) => `- ${file ? `\`${file}\`: ` : ""}${summary}`);
21
+ const deviations = implementation.deviations.map((deviation) => `- ${deviation}`);
22
+ return `${[
23
+ `## Summary\n\n${implementation.summary}`,
24
+ `## Acceptance criteria\n\n| Criterion | Review | Evidence |\n|---|---|---|\n${criteria.join("\n")}`,
25
+ `## Verification\n\n${checks.join("\n")}`,
26
+ `## Review\n\n${rounds}\n\n${minor.length > 0 ? `Minor findings:\n${minor.join("\n")}` : "No minor findings."}`,
27
+ `## Deviations from plan\n\n${deviations.length > 0 ? deviations.join("\n") : "None."}`,
28
+ "---",
29
+ `Spec: \`${facts.specPath}\` at ${facts.baseCommit.slice(0, 7)} · Plan: \`${facts.planPath}\` · Run: ${facts.runId}`,
30
+ ].join("\n\n")}\n`;
31
+ }
32
+ //# sourceMappingURL=pull-request.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pull-request.js","sourceRoot":"","sources":["../../src/domain/pull-request.ts"],"names":[],"mappings":"AAEA,OAAO,EAAoB,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAe9D,qEAAqE;AACrE,SAAS,IAAI,CAAC,IAAY;IACxB,OAAO,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;AAC/D,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,eAAe,CAAC,KAAuB;IACrD,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;IACzC,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;QACnD,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QACxE,MAAM,MAAM,GAAG,MAAM,EAAE,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;QAC5D,OAAO,KAAK,IAAI,CAAC,GAAG,EAAE,KAAK,IAAI,EAAE,CAAC,MAAM,MAAM,MAAM,IAAI,CAAC,MAAM,EAAE,QAAQ,IAAI,EAAE,CAAC,IAAI,CAAC;IACvF,CAAC,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAC7B,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE,CAChC,KAAK,IAAI,OAAO,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,gBAAgB,OAAO,CAAC,UAAU,CAAC,EAAE,CACzE,CAAC;IACF,MAAM,MAAM,GACV,KAAK,CAAC,YAAY,KAAK,CAAC;QACtB,CAAC,CAAC,+BAA+B;QACjC,CAAC,CAAC,kBAAkB,KAAK,CAAC,YAAY,SAAS,KAAK,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC;IACrG,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ;SAC1B,MAAM,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,QAAQ,KAAK,OAAO,CAAC;SAC9C,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;IAC5E,MAAM,UAAU,GAAG,cAAc,CAAC,UAAU,CAAC,GAAG,CAC9C,CAAC,SAAS,EAAE,EAAE,CAAC,KAAK,SAAS,EAAE,CAChC,CAAC;IACF,OAAO,GAAG;QACR,iBAAiB,cAAc,CAAC,OAAO,EAAE;QACzC,+EAA+E,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QACpG,sBAAsB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QACzC,gBAAgB,MAAM,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,oBAAoB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,oBAAoB,EAAE;QAC/G,8BAA8B,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE;QACvF,KAAK;QACL,WAAW,KAAK,CAAC,QAAQ,SAAS,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,cAAc,KAAK,CAAC,QAAQ,aAAa,KAAK,CAAC,KAAK,EAAE;KACrH,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC"}
@@ -0,0 +1,36 @@
1
+ import { z } from "@clemrig/flow";
2
+ import { type Criterion } from "./spec.js";
3
+ /** The implementing agent's response. */
4
+ export declare const implementationOutput: z.ZodObject<{
5
+ summary: z.ZodString;
6
+ criteria: z.ZodArray<z.ZodObject<{
7
+ id: z.ZodString;
8
+ evidence: z.ZodString;
9
+ }, z.core.$strict>>;
10
+ deviations: z.ZodArray<z.ZodString>;
11
+ }, z.core.$strict>;
12
+ export type Implementation = z.output<typeof implementationOutput>;
13
+ export declare function implementationProblem(implementation: Implementation, criteria: Criterion[]): string | undefined;
14
+ /** The reviewer's response: its approval follows from these facts alone. */
15
+ export declare const reviewOutput: z.ZodObject<{
16
+ criteria: z.ZodArray<z.ZodObject<{
17
+ id: z.ZodString;
18
+ status: z.ZodEnum<{
19
+ met: "met";
20
+ not_met: "not_met";
21
+ }>;
22
+ evidence: z.ZodString;
23
+ }, z.core.$strict>>;
24
+ findings: z.ZodArray<z.ZodObject<{
25
+ severity: z.ZodEnum<{
26
+ blocking: "blocking";
27
+ minor: "minor";
28
+ }>;
29
+ file: z.ZodOptional<z.ZodString>;
30
+ summary: z.ZodString;
31
+ }, z.core.$strict>>;
32
+ }, z.core.$strict>;
33
+ export type Review = z.output<typeof reviewOutput>;
34
+ export declare function reviewProblem(review: Review, criteria: Criterion[]): string | undefined;
35
+ /** The correction sent to the implementing agent, or nothing when every criterion is met and nothing blocks. */
36
+ export declare function reviewChanges(review: Review): string | undefined;
@@ -0,0 +1,45 @@
1
+ import { z } from "@clemrig/flow";
2
+ import { citationProblem } from "./spec.js";
3
+ const text = z.string().trim().min(1);
4
+ /** The implementing agent's response. */
5
+ export const implementationOutput = z.strictObject({
6
+ summary: text,
7
+ criteria: z.array(z.strictObject({ id: text, evidence: text })),
8
+ /** Where the work departs from the plan, and why. */
9
+ deviations: z.array(text),
10
+ });
11
+ export function implementationProblem(implementation, criteria) {
12
+ return citationProblem(implementation.criteria.map(({ id }) => id), criteria, true);
13
+ }
14
+ /** The reviewer's response: its approval follows from these facts alone. */
15
+ export const reviewOutput = z.strictObject({
16
+ criteria: z.array(z.strictObject({
17
+ id: text,
18
+ status: z.enum(["met", "not_met"]),
19
+ evidence: text,
20
+ })),
21
+ findings: z.array(z.strictObject({
22
+ /** Blocking: an unmet criterion, a defect or a regression; minor: an improvement not required. */
23
+ severity: z.enum(["blocking", "minor"]),
24
+ file: text.optional(),
25
+ summary: text,
26
+ })),
27
+ });
28
+ export function reviewProblem(review, criteria) {
29
+ return citationProblem(review.criteria.map(({ id }) => id), criteria, true);
30
+ }
31
+ /** The correction sent to the implementing agent, or nothing when every criterion is met and nothing blocks. */
32
+ export function reviewChanges(review) {
33
+ const changes = [
34
+ ...review.criteria
35
+ .filter(({ status }) => status === "not_met")
36
+ .map(({ id, evidence }) => `- ${id} is not met: ${evidence}`),
37
+ ...review.findings
38
+ .filter(({ severity }) => severity === "blocking")
39
+ .map(({ file, summary }) => `- Blocking${file ? ` (${file})` : ""}: ${summary}`),
40
+ ];
41
+ return changes.length > 0
42
+ ? ["The review requires changes:", ...changes].join("\n")
43
+ : undefined;
44
+ }
45
+ //# sourceMappingURL=review.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review.js","sourceRoot":"","sources":["../../src/domain/review.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,eAAe,CAAC;AAClC,OAAO,EAAkB,eAAe,EAAE,MAAM,WAAW,CAAC;AAE5D,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AAEtC,yCAAyC;AACzC,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,YAAY,CAAC;IACjD,OAAO,EAAE,IAAI;IACb,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/D,qDAAqD;IACrD,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC;CAC1B,CAAC,CAAC;AAGH,MAAM,UAAU,qBAAqB,CACnC,cAA8B,EAC9B,QAAqB;IAErB,OAAO,eAAe,CACpB,cAAc,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAC3C,QAAQ,EACR,IAAI,CACL,CAAC;AACJ,CAAC;AAED,4EAA4E;AAC5E,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC,YAAY,CAAC;IACzC,QAAQ,EAAE,CAAC,CAAC,KAAK,CACf,CAAC,CAAC,YAAY,CAAC;QACb,EAAE,EAAE,IAAI;QACR,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;QAClC,QAAQ,EAAE,IAAI;KACf,CAAC,CACH;IACD,QAAQ,EAAE,CAAC,CAAC,KAAK,CACf,CAAC,CAAC,YAAY,CAAC;QACb,kGAAkG;QAClG,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QACvC,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE;QACrB,OAAO,EAAE,IAAI;KACd,CAAC,CACH;CACF,CAAC,CAAC;AAGH,MAAM,UAAU,aAAa,CAC3B,MAAc,EACd,QAAqB;IAErB,OAAO,eAAe,CACpB,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EACnC,QAAQ,EACR,IAAI,CACL,CAAC;AACJ,CAAC;AAED,gHAAgH;AAChH,MAAM,UAAU,aAAa,CAAC,MAAc;IAC1C,MAAM,OAAO,GAAG;QACd,GAAG,MAAM,CAAC,QAAQ;aACf,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,MAAM,KAAK,SAAS,CAAC;aAC5C,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,KAAK,EAAE,gBAAgB,QAAQ,EAAE,CAAC;QAC/D,GAAG,MAAM,CAAC,QAAQ;aACf,MAAM,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,QAAQ,KAAK,UAAU,CAAC;aACjD,GAAG,CACF,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,CACpB,aAAa,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,OAAO,EAAE,CACtD;KACJ,CAAC;IACF,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC;QACvB,CAAC,CAAC,CAAC,8BAA8B,EAAE,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;QACzD,CAAC,CAAC,SAAS,CAAC;AAChB,CAAC"}
@@ -0,0 +1,18 @@
1
+ import type { Parsed } from "./parsed.js";
2
+ /** An acceptance criterion: an externally observable fact, cited by the plan, the review and the PR. */
3
+ export interface Criterion {
4
+ id: string;
5
+ text: string;
6
+ }
7
+ export interface Spec {
8
+ title: string;
9
+ objective: string;
10
+ scope: string;
11
+ outOfScope: string;
12
+ criteria: Criterion[];
13
+ constraints: string;
14
+ }
15
+ /** Reads spec.md: a `# ` title, then exactly the five sections, in order. */
16
+ export declare function parseSpec(text: string): Parsed<Spec>;
17
+ /** What is wrong with the criteria a response cites: all of the spec's, no other, and each once if required. */
18
+ export declare function citationProblem(cited: string[], criteria: Criterion[], once?: boolean): string | undefined;
@@ -0,0 +1,118 @@
1
+ const sectionHeadings = [
2
+ "Objective",
3
+ "Scope",
4
+ "Out of scope",
5
+ "Acceptance criteria",
6
+ "Constraints",
7
+ ];
8
+ /** Reads spec.md: a `# ` title, then exactly the five sections, in order. */
9
+ export function parseSpec(text) {
10
+ const [first = "", ...lines] = text.trim().split(/\r?\n/);
11
+ const title = /^# (.*)$/.exec(first)?.[1]?.trim() ?? "";
12
+ const preamble = [];
13
+ const headings = [];
14
+ const sections = new Map();
15
+ let current = preamble;
16
+ for (const line of lines) {
17
+ const heading = /^## (.*)$/.exec(line)?.[1]?.trim();
18
+ if (heading === undefined)
19
+ current.push(line);
20
+ else {
21
+ current = [];
22
+ headings.push(heading);
23
+ sections.set(heading, current);
24
+ }
25
+ }
26
+ const body = (heading) => (sections.get(heading) ?? []).join("\n").trim();
27
+ const { criteria, problems: criteriaProblems } = parseCriteria(sections.get("Acceptance criteria") ?? []);
28
+ const problems = [
29
+ ...(title ? [] : ["The first line must be a title: # <Title>"]),
30
+ ...(preamble.join("").trim()
31
+ ? ["Unexpected text before the first section"]
32
+ : []),
33
+ ...sectionProblems(headings),
34
+ ...sectionHeadings
35
+ .filter((heading) => sections.has(heading) && !body(heading))
36
+ .map((heading) => `Empty section: ## ${heading}`),
37
+ ...criteriaProblems,
38
+ ];
39
+ if (problems.length > 0)
40
+ return { ok: false, problems };
41
+ return {
42
+ ok: true,
43
+ value: {
44
+ title,
45
+ objective: body("Objective"),
46
+ scope: body("Scope"),
47
+ outOfScope: body("Out of scope"),
48
+ criteria,
49
+ constraints: body("Constraints"),
50
+ },
51
+ };
52
+ }
53
+ /** Exactly the expected headings, each once, in order. */
54
+ function sectionProblems(headings) {
55
+ const unknown = headings.filter((heading) => !sectionHeadings.includes(heading));
56
+ const duplicate = sectionHeadings.filter((heading) => headings.indexOf(heading) !== headings.lastIndexOf(heading));
57
+ const missing = sectionHeadings.filter((heading) => !headings.includes(heading));
58
+ const problems = [
59
+ ...[...new Set(unknown)].map((heading) => `Unknown section: ## ${heading}`),
60
+ ...duplicate.map((heading) => `Duplicate section: ## ${heading}`),
61
+ ...missing.map((heading) => `Missing section: ## ${heading}`),
62
+ ];
63
+ if (problems.length === 0 &&
64
+ headings.join("\n") !== sectionHeadings.join("\n"))
65
+ problems.push(`Sections must appear in this order: ${sectionHeadings.join(", ")}`);
66
+ return problems;
67
+ }
68
+ /** `- AC<n>: <text>` items numbered from 1; an indented line continues the item above it. */
69
+ function parseCriteria(lines) {
70
+ const criteria = [];
71
+ const problems = [];
72
+ let open;
73
+ const close = () => {
74
+ if (open && !open.text)
75
+ problems.push(`Acceptance criteria: ${open.id} is empty`);
76
+ open = undefined;
77
+ };
78
+ for (const line of lines) {
79
+ if (!line.trim())
80
+ continue;
81
+ const item = /^- (AC\d+):(.*)$/.exec(line);
82
+ if (item) {
83
+ close();
84
+ const [, id = "", text = ""] = item;
85
+ const expected = `AC${criteria.length + 1}`;
86
+ if (id !== expected)
87
+ problems.push(`Acceptance criteria: expected ${expected}, found ${id}`);
88
+ open = { id, text: text.trim() };
89
+ criteria.push(open);
90
+ }
91
+ else if (open && /^\s/.test(line))
92
+ open.text = `${open.text} ${line.trim()}`.trim();
93
+ else {
94
+ close();
95
+ problems.push(`Acceptance criteria: unexpected line "${line.trim()}" (write "- AC<n>: <criterion>")`);
96
+ }
97
+ }
98
+ close();
99
+ return { criteria, problems };
100
+ }
101
+ /** What is wrong with the criteria a response cites: all of the spec's, no other, and each once if required. */
102
+ export function citationProblem(cited, criteria, once = false) {
103
+ const ids = criteria.map((criterion) => criterion.id);
104
+ const missing = ids.filter((id) => !cited.includes(id));
105
+ const unknown = [...new Set(cited.filter((id) => !ids.includes(id)))];
106
+ const repeated = once
107
+ ? [...new Set(cited.filter((id, index) => cited.indexOf(id) !== index))]
108
+ : [];
109
+ const problems = [
110
+ ...(missing.length > 0 ? [`missing ${missing.join(", ")}`] : []),
111
+ ...(unknown.length > 0 ? [`unknown ${unknown.join(", ")}`] : []),
112
+ ...(repeated.length > 0 ? [`repeated ${repeated.join(", ")}`] : []),
113
+ ];
114
+ return problems.length > 0
115
+ ? `Acceptance criteria: ${problems.join("; ")}`
116
+ : undefined;
117
+ }
118
+ //# sourceMappingURL=spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec.js","sourceRoot":"","sources":["../../src/domain/spec.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /** A command of the repository that must succeed, as its definition declares it. */
2
+ export interface VerifyCommand {
3
+ name: string;
4
+ command: string[];
5
+ timeoutMs: number;
6
+ }
7
+ /** A check that passed, as the pull request reports it. */
8
+ export interface CheckResult {
9
+ name: string;
10
+ command: string[];
11
+ durationMs: number;
12
+ }
13
+ /** The correction sent to the implementing agent: the failed check, its cause and the end of its output. */
14
+ export declare function failureFeedback(check: VerifyCommand, cause: string, output: string): string;
15
+ /** A duration as the terminal and the pull request show it. */
16
+ export declare function seconds(durationMs: number): string;