@papi-ai/adapter-md 0.1.0-alpha → 0.1.1-alpha

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 (4) hide show
  1. package/LICENSE +98 -0
  2. package/dist/index.d.ts +368 -187
  3. package/dist/index.js +278 -417
  4. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -36,8 +36,8 @@ function extractSectionCompat(content, newHeading, legacyHeading) {
36
36
  const section = extractSection(content, newHeading);
37
37
  return section || extractSection(content, legacyHeading);
38
38
  }
39
- function parseSprintHealth(content) {
40
- const section = extractSectionCompat(content, "Sprint Health", "Session Health");
39
+ function parseCycleHealth(content) {
40
+ const section = extractSectionCompat(content, "Cycle Health", "Sprint Health") || extractSection(content, "Session Health");
41
41
  const rows = /* @__PURE__ */ new Map();
42
42
  for (const line of section.split("\n")) {
43
43
  const match = line.match(/^\|\s*(.+?)\s*\|\s*(.+?)\s*\|$/);
@@ -48,27 +48,29 @@ function parseSprintHealth(content) {
48
48
  }
49
49
  const get = (key) => rows.get(key) ?? "";
50
50
  return {
51
- totalSprints: parseInt(get("total sprints") || get("total sessions"), 10) || 0,
52
- sprintsSinceLastStrategyReview: parseInt(get("sprints since last strategy review") || get("sessions since last strategy review"), 10) || 0,
51
+ totalCycles: parseInt(get("total cycles") || get("total sprints") || get("total sessions"), 10) || 0,
52
+ cyclesSinceLastStrategyReview: parseInt(get("cycles since last strategy review") || get("sprints since last strategy review") || get("sessions since last strategy review"), 10) || 0,
53
53
  strategyReviewDue: get("strategy review due"),
54
54
  boardHealth: get("board health"),
55
55
  strategicDirection: get("strategic direction"),
56
56
  lastFullMode: parseInt(get("last full mode"), 10) || 0
57
57
  };
58
58
  }
59
- function serializeSprintHealth(health, content) {
60
- const section = extractSectionCompat(content, "Sprint Health", "Session Health");
59
+ function serializeCycleHealth(health, content) {
60
+ const section = extractSectionCompat(content, "Cycle Health", "Sprint Health") || extractSection(content, "Session Health");
61
61
  const fieldMap = {
62
- "Total sprints": String(health.totalSprints),
63
- "Sprints since last Strategy Review": String(health.sprintsSinceLastStrategyReview),
62
+ "Total cycles": String(health.totalCycles),
63
+ "Cycles since last Strategy Review": String(health.cyclesSinceLastStrategyReview),
64
64
  "Strategy Review due": health.strategyReviewDue,
65
65
  "Board health": health.boardHealth,
66
66
  "Strategic direction": health.strategicDirection,
67
67
  "Last Full Mode": String(health.lastFullMode)
68
68
  };
69
69
  const legacyFieldMap = {
70
- "Total sessions": String(health.totalSprints),
71
- "Sessions since last Strategy Review": String(health.sprintsSinceLastStrategyReview)
70
+ "Total sprints": String(health.totalCycles),
71
+ "Sprints since last Strategy Review": String(health.cyclesSinceLastStrategyReview),
72
+ "Total sessions": String(health.totalCycles),
73
+ "Sessions since last Strategy Review": String(health.cyclesSinceLastStrategyReview)
72
74
  };
73
75
  let updatedSection = section;
74
76
  for (const [metric, value] of Object.entries({ ...fieldMap, ...legacyFieldMap })) {
@@ -86,8 +88,8 @@ function parseActiveDecisions(content) {
86
88
  );
87
89
  if (!headingMatch) return null;
88
90
  const metaMatch = block.match(/<!-- papi:(?:created_sprint=(\d+))?\s*(?:modified_sprint=(\d+)\s*)*(?:uuid=(\S+))? -->/);
89
- const createdSprint = metaMatch?.[1] ? parseInt(metaMatch[1], 10) : void 0;
90
- const modifiedSprint = metaMatch?.[2] ? parseInt(metaMatch[2], 10) : void 0;
91
+ const createdCycle = metaMatch?.[1] ? parseInt(metaMatch[1], 10) : void 0;
92
+ const modifiedCycle = metaMatch?.[2] ? parseInt(metaMatch[2], 10) : void 0;
91
93
  const uuid = metaMatch?.[3] ?? randomUUID();
92
94
  return {
93
95
  uuid,
@@ -97,8 +99,8 @@ function parseActiveDecisions(content) {
97
99
  confidence: headingMatch[3] ?? "HIGH",
98
100
  superseded: !!headingMatch[4],
99
101
  supersededBy: headingMatch[4],
100
- createdSprint,
101
- modifiedSprint,
102
+ createdCycle,
103
+ modifiedCycle,
102
104
  body: block
103
105
  };
104
106
  }).filter((d) => d !== null);
@@ -106,16 +108,16 @@ function parseActiveDecisions(content) {
106
108
  function stripTemporalMeta(body) {
107
109
  return body.replace(/\n?<!-- papi:(?:created_sprint=\d+)?\s*(?:modified_sprint=\d+\s*)*(?:uuid=\S+)? -->/g, "");
108
110
  }
109
- function buildTemporalMeta(createdSprint, modifiedSprint, uuid) {
111
+ function buildTemporalMeta(createdCycle, modifiedCycle, uuid) {
110
112
  const parts = [];
111
- if (createdSprint != null) parts.push(`created_sprint=${createdSprint}`);
112
- if (modifiedSprint != null) parts.push(`modified_sprint=${modifiedSprint}`);
113
+ if (createdCycle != null) parts.push(`created_sprint=${createdCycle}`);
114
+ if (modifiedCycle != null) parts.push(`modified_sprint=${modifiedCycle}`);
113
115
  if (uuid) parts.push(`uuid=${uuid}`);
114
116
  if (parts.length === 0) return "";
115
117
  return `
116
118
  <!-- papi:${parts.join(" ")} -->`;
117
119
  }
118
- function extractCreatedSprint(block) {
120
+ function extractCreatedCycle(block) {
119
121
  const m = block.match(/<!-- papi:(?:created_sprint=(\d+))/);
120
122
  return m?.[1] ? parseInt(m[1], 10) : void 0;
121
123
  }
@@ -123,19 +125,19 @@ function extractUuid(block) {
123
125
  const m = block.match(/<!-- papi:.*?uuid=(\S+)/);
124
126
  return m?.[1];
125
127
  }
126
- function updateActiveDecisionInContent(id, newBody, content, sprintNumber) {
128
+ function updateActiveDecisionInContent(id, newBody, content, cycleNumber) {
127
129
  if (!newBody) return content;
128
130
  const escapedId = id.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
129
131
  const pattern = new RegExp(`(### ${escapedId}:.*?)(?=^### AD-\\d+:|^## |$(?![\\s\\S]))`, "ms");
130
132
  const cleanBody = stripTemporalMeta(newBody);
131
133
  if (pattern.test(content)) {
132
134
  const existingMatch = content.match(pattern);
133
- const existingCreated = existingMatch ? extractCreatedSprint(existingMatch[0]) : void 0;
135
+ const existingCreated = existingMatch ? extractCreatedCycle(existingMatch[0]) : void 0;
134
136
  const existingUuid = existingMatch ? extractUuid(existingMatch[0]) : void 0;
135
- const meta2 = sprintNumber != null ? buildTemporalMeta(existingCreated, sprintNumber, existingUuid) : existingUuid ? buildTemporalMeta(existingCreated, void 0, existingUuid) : "";
137
+ const meta2 = cycleNumber != null ? buildTemporalMeta(existingCreated, cycleNumber, existingUuid) : existingUuid ? buildTemporalMeta(existingCreated, void 0, existingUuid) : "";
136
138
  return content.replace(pattern, cleanBody.trimEnd() + meta2 + "\n\n");
137
139
  }
138
- const meta = sprintNumber != null ? buildTemporalMeta(sprintNumber) : "";
140
+ const meta = cycleNumber != null ? buildTemporalMeta(cycleNumber) : "";
139
141
  const sectionPattern = /^(#{1,2} Active Decisions\n)([\s\S]*?)(?=^#{1,2} |$(?![\s\S]))/m;
140
142
  const sectionMatch = content.match(sectionPattern);
141
143
  if (sectionMatch) {
@@ -146,22 +148,22 @@ function updateActiveDecisionInContent(id, newBody, content, sprintNumber) {
146
148
  }
147
149
  return content;
148
150
  }
149
- function parseSprintLog(content, limit) {
150
- const section = extractSectionCompat(content, "Sprint Log", "Session Log");
151
- const chunks = section.split(/^(?=### (?:Sprint|Session) \d+ —)/m).map((c) => c.trim()).filter((c) => c.match(/^### (?:Sprint|Session) \d+ —/));
151
+ function parseCycleLog(content, limit) {
152
+ const section = extractSectionCompat(content, "Cycle Log", "Sprint Log");
153
+ const chunks = section.split(/^(?=### (?:Cycle|Sprint|Session) \d+ —)/m).map((c) => c.trim()).filter((c) => c.match(/^### (?:Cycle|Sprint|Session) \d+ —/));
152
154
  const entries = chunks.map((block) => {
153
- const headingMatch = block.match(/^### (?:Sprint|Session) (\d+) — (.+?)$/m);
154
- const sprintNumber = headingMatch ? parseInt(headingMatch[1], 10) : 0;
155
+ const headingMatch = block.match(/^### (?:Cycle|Sprint|Session) (\d+) — (.+?)$/m);
156
+ const cycleNumber = headingMatch ? parseInt(headingMatch[1], 10) : 0;
155
157
  const title = headingMatch ? headingMatch[2].trim() : block.split("\n")[0].replace(/^### /, "");
156
158
  const carryForwardMatch = block.match(/^- \*\*CARRY FORWARD:\*\*\s*(.+)$/m);
157
159
  const uuidMatch = block.match(/<!-- papi:.*?uuid=(\S+)/);
158
160
  const uuid = uuidMatch?.[1];
159
161
  const blockClean = block.replace(/\n?<!-- papi:.*?-->/g, "");
160
- const notesMatch = blockClean.match(/\*\*Sprint Notes:\*\*\s*([\s\S]*?)$/);
162
+ const notesMatch = blockClean.match(/\*\*Cycle Notes:\*\*\s*([\s\S]*?)$/);
161
163
  const notes = notesMatch ? notesMatch[1].trim() : void 0;
162
164
  return {
163
165
  uuid: uuid ?? randomUUID(),
164
- sprintNumber,
166
+ cycleNumber,
165
167
  title,
166
168
  content: block,
167
169
  carryForward: carryForwardMatch ? carryForwardMatch[1].trim() : void 0,
@@ -170,11 +172,11 @@ function parseSprintLog(content, limit) {
170
172
  });
171
173
  return limit ? entries.slice(0, limit) : entries;
172
174
  }
173
- function prependSprintLogEntry(entry, content) {
174
- const headingPattern = /^## (?:Sprint|Session) Log\s*$/m;
175
+ function prependCycleLogEntry(entry, content) {
176
+ const headingPattern = /^## (?:Cycle|Sprint|Session) Log\s*$/m;
175
177
  const headingMatch = content.match(headingPattern);
176
178
  if (!headingMatch || headingMatch.index === void 0) {
177
- throw new Error("Sprint Log section not found in Planning Log");
179
+ throw new Error("Cycle Log section not found in Planning Log");
178
180
  }
179
181
  const insertPos = headingMatch.index + headingMatch[0].length;
180
182
  const before = content.slice(0, insertPos);
@@ -183,7 +185,7 @@ function prependSprintLogEntry(entry, content) {
183
185
  if (entry.notes) {
184
186
  entryContent = `${entryContent}
185
187
 
186
- **Sprint Notes:** ${entry.notes}`;
188
+ **Cycle Notes:** ${entry.notes}`;
187
189
  }
188
190
  if (entry.uuid) {
189
191
  entryContent = `${entryContent}
@@ -201,13 +203,13 @@ function parseDeferred(content) {
201
203
  const section = extractSection(content, "Deferred / Parking Lot");
202
204
  return section.split("\n").filter((line) => line.match(/^-\s+/)).map((line) => line.replace(/^-\s+/, "").trim());
203
205
  }
204
- function compressSprintLogInContent(content, threshold, summary) {
205
- const section = extractSectionCompat(content, "Sprint Log", "Session Log");
206
- const chunks = section.split(/^(?=### (?:Sprint|Session) \d+ —)/m).map((c) => c.trim()).filter((c) => c.match(/^### (?:Sprint|Session) \d+ —/));
206
+ function compressCycleLogInContent(content, threshold, summary) {
207
+ const section = extractSectionCompat(content, "Cycle Log", "Sprint Log");
208
+ const chunks = section.split(/^(?=### (?:Cycle|Sprint|Session) \d+ —)/m).map((c) => c.trim()).filter((c) => c.match(/^### (?:Cycle|Sprint|Session) \d+ —/));
207
209
  const keep = [];
208
210
  let hasOld = false;
209
211
  for (const block of chunks) {
210
- const match = block.match(/^### (?:Sprint|Session) (\d+) —/);
212
+ const match = block.match(/^### (?:Cycle|Sprint|Session) (\d+) —/);
211
213
  const num = match ? parseInt(match[1], 10) : 0;
212
214
  if (num >= threshold) {
213
215
  keep.push(block);
@@ -216,27 +218,27 @@ function compressSprintLogInContent(content, threshold, summary) {
216
218
  }
217
219
  }
218
220
  if (!hasOld) return content;
219
- const summaryBlock = `### Sprints 1\u2013${threshold - 1} \u2014 Compressed Summary
221
+ const summaryBlock = `### Cycles 1\u2013${threshold - 1} \u2014 Compressed Summary
220
222
 
221
223
  ${summary}`;
222
224
  const newEntries = [...keep, summaryBlock].join("\n\n");
223
- const newSection = `## Sprint Log
225
+ const newSection = `## Cycle Log
224
226
 
225
227
  ${newEntries}
226
228
  `;
227
229
  return content.replace(section, newSection);
228
230
  }
229
- function parsePlanningLog(content, activeDecisionsContent, sprintLogContent) {
231
+ function parsePlanningLog(content, activeDecisionsContent, cycleLogContent) {
230
232
  return {
231
- sprintHealth: parseSprintHealth(content),
233
+ cycleHealth: parseCycleHealth(content),
232
234
  northStar: parseNorthStar(content),
233
235
  activeDecisions: parseActiveDecisions(activeDecisionsContent ?? content),
234
236
  deferred: parseDeferred(content),
235
- sprintLog: sprintLogContent ? parseSprintLog(sprintLogContent) : []
237
+ cycleLog: cycleLogContent ? parseCycleLog(cycleLogContent) : []
236
238
  };
237
239
  }
238
240
 
239
- // src/parsers/sprint-board.ts
241
+ // src/parsers/cycle-board.ts
240
242
  import { randomUUID as randomUUID3 } from "crypto";
241
243
  import yaml from "js-yaml";
242
244
 
@@ -285,11 +287,11 @@ function parseBuildHandoff(markdown) {
285
287
  if (!markdown.includes("BUILD HANDOFF")) return null;
286
288
  const taskIdMatch = markdown.match(/BUILD HANDOFF\s*—\s*(task-\d+)/);
287
289
  const taskTitleMatch = markdown.match(/^Task:\s*(.+)$/m);
288
- const sprintMatch = markdown.match(/^Sprint:\s*(\d+)$/m);
290
+ const cycleMatch = markdown.match(/^Cycle:\s*(\d+)$/m);
289
291
  const whyNowMatch = markdown.match(/^Why now:\s*([\s\S]*?)(?=\n\n|\nSCOPE)/m);
290
292
  const taskId = taskIdMatch?.[1] ?? "";
291
293
  const taskTitle = taskTitleMatch?.[1]?.trim() ?? "";
292
- const sprint = sprintMatch ? parseInt(sprintMatch[1], 10) : 0;
294
+ const cycle = cycleMatch ? parseInt(cycleMatch[1], 10) : 0;
293
295
  const whyNow = whyNowMatch?.[1]?.replace(/\s+/g, " ").trim() ?? "";
294
296
  const uuidMatch = markdown.match(/^UUID:\s*(\S+)$/m);
295
297
  const uuid = uuidMatch?.[1];
@@ -306,7 +308,7 @@ function parseBuildHandoff(markdown) {
306
308
  ...createdAt ? { createdAt } : {},
307
309
  taskId,
308
310
  taskTitle,
309
- sprint,
311
+ cycle,
310
312
  whyNow,
311
313
  scope: parseBulletList(sections.get("SCOPE (DO THIS)") ?? ""),
312
314
  scopeBoundary: parseBulletList(sections.get("SCOPE BOUNDARY (DO NOT DO THIS)") ?? ""),
@@ -323,7 +325,7 @@ function serializeBuildHandoff(handoff) {
323
325
  if (handoff.displayId) lines.push(`Display ID: ${handoff.displayId}`);
324
326
  if (handoff.createdAt) lines.push(`Created: ${handoff.createdAt}`);
325
327
  lines.push(`Task: ${handoff.taskTitle}`);
326
- lines.push(`Sprint: ${handoff.sprint}`);
328
+ lines.push(`Cycle: ${handoff.cycle}`);
327
329
  lines.push(`Why now: ${handoff.whyNow}`);
328
330
  lines.push("");
329
331
  lines.push("SCOPE (DO THIS)");
@@ -354,11 +356,11 @@ function serializeBuildHandoff(handoff) {
354
356
  return lines.join("\n");
355
357
  }
356
358
 
357
- // src/parsers/sprint-board.ts
359
+ // src/parsers/cycle-board.ts
358
360
  var YAML_MARKER = "<!-- PAPI-ADAPTER: parse the yaml block below -->";
359
361
  var YAML_START = "<!-- PAPI-YAML-START -->";
360
362
  var YAML_END = "<!-- PAPI-YAML-END -->";
361
- function toSprintTask(raw) {
363
+ function toCycleTask(raw) {
362
364
  return {
363
365
  uuid: raw.uuid || randomUUID3(),
364
366
  id: raw.id,
@@ -372,8 +374,8 @@ function toSprintTask(raw) {
372
374
  phase: raw.phase,
373
375
  owner: raw.owner,
374
376
  reviewed: raw.reviewed ?? false,
375
- sprint: raw.sprint != null ? raw.sprint : void 0,
376
- createdSprint: raw.created_sprint != null ? raw.created_sprint : void 0,
377
+ cycle: raw.cycle != null ? raw.cycle : void 0,
378
+ createdCycle: raw.created_sprint != null ? raw.created_sprint : void 0,
377
379
  createdAt: raw.created_at || void 0,
378
380
  why: raw.why || void 0,
379
381
  dependsOn: raw.depends_on || void 0,
@@ -387,7 +389,7 @@ function toSprintTask(raw) {
387
389
  function sanitizeDelimiters(value) {
388
390
  return value.replaceAll(YAML_END, "<!-- PAPI-YAML-END (sanitized) -->");
389
391
  }
390
- function fromSprintTask(task) {
392
+ function fromCycleTask(task) {
391
393
  const raw = {
392
394
  uuid: task.uuid,
393
395
  id: task.id,
@@ -403,8 +405,8 @@ function fromSprintTask(task) {
403
405
  depends_on: task.dependsOn ?? "",
404
406
  notes: task.notes ? sanitizeDelimiters(task.notes) : ""
405
407
  };
406
- if (task.sprint != null) raw.sprint = task.sprint;
407
- if (task.createdSprint != null) raw.created_sprint = task.createdSprint;
408
+ if (task.cycle != null) raw.cycle = task.cycle;
409
+ if (task.createdCycle != null) raw.created_sprint = task.createdCycle;
408
410
  if (task.createdAt) raw.created_at = task.createdAt;
409
411
  if (task.why) raw.why = task.why;
410
412
  if (task.stateHistory?.length) {
@@ -423,17 +425,17 @@ function mergeConflictHint(content) {
423
425
  }
424
426
  function extractYamlBlock(content) {
425
427
  const markerIdx = content.indexOf(YAML_MARKER);
426
- if (markerIdx === -1) throw new Error("PAPI-ADAPTER marker not found in SPRINT_BOARD.md");
428
+ if (markerIdx === -1) throw new Error("PAPI-ADAPTER marker not found in CYCLE_BOARD.md");
427
429
  const afterMarker = content.slice(markerIdx + YAML_MARKER.length);
428
430
  const startIdx = afterMarker.indexOf(YAML_START);
429
431
  if (startIdx !== -1) {
430
432
  const yamlStart = startIdx + YAML_START.length;
431
433
  const endIdx = afterMarker.indexOf(YAML_END, yamlStart);
432
- if (endIdx === -1) throw new Error("PAPI-YAML-END marker not found in SPRINT_BOARD.md");
434
+ if (endIdx === -1) throw new Error("PAPI-YAML-END marker not found in CYCLE_BOARD.md");
433
435
  return afterMarker.slice(yamlStart, endIdx);
434
436
  }
435
437
  const blockMatch = afterMarker.match(/```yaml\n([\s\S]*?)```/);
436
- if (!blockMatch) throw new Error("YAML block not found in SPRINT_BOARD.md");
438
+ if (!blockMatch) throw new Error("YAML block not found in CYCLE_BOARD.md");
437
439
  return blockMatch[1];
438
440
  }
439
441
  function parseBoard(content) {
@@ -446,27 +448,27 @@ function parseBoard(content) {
446
448
  const lineInfo = yamlErr.mark?.line != null ? ` (near line ${yamlErr.mark.line + 1} of YAML block)` : "";
447
449
  const hint = mergeConflictHint(yamlText);
448
450
  throw new Error(
449
- `YAML parse error in SPRINT_BOARD.md${lineInfo}. Check for syntax errors \u2014 unquoted special characters, bad indentation, or missing colons.${hint}`
451
+ `YAML parse error in CYCLE_BOARD.md${lineInfo}. Check for syntax errors \u2014 unquoted special characters, bad indentation, or missing colons.${hint}`
450
452
  );
451
453
  }
452
- return (data.tasks ?? []).map(toSprintTask);
454
+ return (data.tasks ?? []).map(toCycleTask);
453
455
  }
454
456
  function serializeBoard(tasks, content) {
455
- const raw = tasks.map(fromSprintTask);
457
+ const raw = tasks.map(fromCycleTask);
456
458
  const yamlStr = yaml.dump({ tasks: raw }, { lineWidth: 120, quotingType: '"' });
457
459
  const markerIdx = content.indexOf(YAML_MARKER);
458
- if (markerIdx === -1) throw new Error("PAPI-ADAPTER marker not found in SPRINT_BOARD.md");
460
+ if (markerIdx === -1) throw new Error("PAPI-ADAPTER marker not found in CYCLE_BOARD.md");
459
461
  const afterMarker = content.slice(markerIdx + YAML_MARKER.length);
460
462
  const htmlStartIdx = afterMarker.indexOf(YAML_START);
461
463
  if (htmlStartIdx !== -1) {
462
464
  const absStart = markerIdx + YAML_MARKER.length + htmlStartIdx;
463
465
  const endIdx = afterMarker.indexOf(YAML_END, htmlStartIdx);
464
- if (endIdx === -1) throw new Error("PAPI-YAML-END marker not found in SPRINT_BOARD.md");
466
+ if (endIdx === -1) throw new Error("PAPI-YAML-END marker not found in CYCLE_BOARD.md");
465
467
  const absEnd = markerIdx + YAML_MARKER.length + endIdx + YAML_END.length;
466
468
  return content.slice(0, absStart) + YAML_START + "\n" + yamlStr + YAML_END + content.slice(absEnd);
467
469
  }
468
470
  const blockMatch = afterMarker.match(/```yaml\n[\s\S]*?```/);
469
- if (!blockMatch) throw new Error("YAML block not found in SPRINT_BOARD.md");
471
+ if (!blockMatch) throw new Error("YAML block not found in CYCLE_BOARD.md");
470
472
  const blockStart = markerIdx + YAML_MARKER.length + afterMarker.indexOf(blockMatch[0]);
471
473
  const blockEnd = blockStart + blockMatch[0].length;
472
474
  return content.slice(0, blockStart) + YAML_START + "\n" + yamlStr + YAML_END + content.slice(blockEnd);
@@ -508,9 +510,9 @@ function parseEffort(effortLine) {
508
510
  return match ? { actual: match[1], estimated: match[2] } : { actual: effortLine, estimated: "" };
509
511
  }
510
512
  function parseBuildReports(content) {
511
- const chunks = content.split(/^(?=### .+ — .+ — (?:Sprint|Session) \d+)/m).map((c) => c.trim()).filter((c) => c.match(/^### .+ — .+ — (?:Sprint|Session) \d+/));
513
+ const chunks = content.split(/^(?=### .+ — .+ — (?:Cycle|Sprint|Session) \d+)/m).map((c) => c.trim()).filter((c) => c.match(/^### .+ — .+ — (?:Cycle|Sprint|Session) \d+/));
512
514
  return chunks.map((block) => {
513
- const headingMatch = block.match(/^### (.+?) — (.+?) — (?:Sprint|Session) (\d+)/);
515
+ const headingMatch = block.match(/^### (.+?) — (.+?) — (?:Cycle|Sprint|Session) (\d+)/);
514
516
  if (!headingMatch) return null;
515
517
  const effortLine = parseField(block, "Actual Effort");
516
518
  const { actual, estimated } = parseEffort(effortLine);
@@ -533,7 +535,7 @@ function parseBuildReports(content) {
533
535
  taskId,
534
536
  taskName: headingMatch[1].trim(),
535
537
  date: headingMatch[2].trim(),
536
- sprint: parseInt(headingMatch[3], 10),
538
+ cycle: parseInt(headingMatch[3], 10),
537
539
  completed: completedRaw.startsWith("Yes") ? "Yes" : completedRaw.startsWith("No") ? "No" : "Partial",
538
540
  actualEffort,
539
541
  estimatedEffort,
@@ -549,7 +551,7 @@ function parseBuildReports(content) {
549
551
  }
550
552
  function serializeBuildReport(report) {
551
553
  const lines = [
552
- `### ${report.taskName} \u2014 ${report.date} \u2014 Sprint ${report.sprint}`
554
+ `### ${report.taskName} \u2014 ${report.date} \u2014 Cycle ${report.cycle}`
553
555
  ];
554
556
  if (report.uuid) lines.push(`- **UUID:** ${report.uuid}`);
555
557
  if (report.displayId) lines.push(`- **Display ID:** ${report.displayId}`);
@@ -569,14 +571,14 @@ function serializeBuildReport(report) {
569
571
  }
570
572
  return lines.join("\n");
571
573
  }
572
- function formatCompressedSummary(reports, sprintRange, aiSummary) {
574
+ function formatCompressedSummary(reports, cycleRange, aiSummary) {
573
575
  const dates = reports.map((r) => r.date).filter(Boolean);
574
576
  const dateRange = dates.length > 0 ? `${dates[dates.length - 1]} \u2013 ${dates[0]}` : "unknown";
575
577
  const completed = reports.filter((r) => r.completed === "Yes");
576
578
  const partial = reports.filter((r) => r.completed === "Partial");
577
579
  const failed = reports.filter((r) => r.completed === "No");
578
580
  const formatTaskList = (list) => list.map((r) => r.taskId !== "unknown" ? `${r.taskId} (${r.taskName})` : r.taskName).join(", ");
579
- const lines = [`### ${sprintRange} \u2014 Compressed Summary`];
581
+ const lines = [`### ${cycleRange} \u2014 Compressed Summary`];
580
582
  lines.push(`**Date range:** ${dateRange}`);
581
583
  lines.push(`**Reports:** ${reports.length}`);
582
584
  if (completed.length > 0) {
@@ -602,11 +604,11 @@ function formatCompressedSummary(reports, sprintRange, aiSummary) {
602
604
  return lines.join("\n");
603
605
  }
604
606
  function compressBuildReportsInContent(content, threshold, summary) {
605
- const chunks = content.split(/^(?=### .+ — .+ — (?:Sprint|Session) \d+)/m).map((c) => c.trim()).filter((c) => c.match(/^### .+ — .+ — (?:Sprint|Session) \d+/));
607
+ const chunks = content.split(/^(?=### .+ — .+ — (?:Cycle|Sprint|Session) \d+)/m).map((c) => c.trim()).filter((c) => c.match(/^### .+ — .+ — (?:Cycle|Sprint|Session) \d+/));
606
608
  const keep = [];
607
609
  const oldChunks = [];
608
610
  for (const block of chunks) {
609
- const match = block.match(/— (?:Sprint|Session) (\d+)/);
611
+ const match = block.match(/— (?:Cycle|Sprint|Session) (\d+)/);
610
612
  const num = match ? parseInt(match[1], 10) : 0;
611
613
  if (num >= threshold) {
612
614
  keep.push(block);
@@ -616,8 +618,8 @@ function compressBuildReportsInContent(content, threshold, summary) {
616
618
  }
617
619
  if (oldChunks.length === 0) return content;
618
620
  const oldReports = parseBuildReports(oldChunks.join("\n\n---\n\n"));
619
- const sprintRange = `Sprints 1\u2013${threshold - 1}`;
620
- const summaryBlock = formatCompressedSummary(oldReports, sprintRange, summary);
621
+ const cycleRange = `Cycles 1\u2013${threshold - 1}`;
622
+ const summaryBlock = formatCompressedSummary(oldReports, cycleRange, summary);
621
623
  const firstReportIdx = content.search(/^### /m);
622
624
  const header = firstReportIdx === -1 ? content : content.slice(0, firstReportIdx);
623
625
  const entries = [...keep, summaryBlock].join("\n\n---\n\n");
@@ -638,7 +640,7 @@ function mergeBuildReports(existing, incoming) {
638
640
  taskId: incoming.taskId,
639
641
  taskName: incoming.taskName,
640
642
  date: incoming.date,
641
- sprint: incoming.sprint,
643
+ cycle: incoming.cycle,
642
644
  completed: incoming.completed,
643
645
  actualEffort: incoming.actualEffort,
644
646
  estimatedEffort: incoming.estimatedEffort,
@@ -659,8 +661,12 @@ function replaceBuildReport(existing, replacement, content) {
659
661
  if (idx !== -1) {
660
662
  return content.slice(0, idx) + newSerialized + content.slice(idx + oldSerialized.length);
661
663
  }
662
- const headingPattern = `### ${existing.taskName} \u2014 ${existing.date} \u2014 Sprint ${existing.sprint}`;
663
- const headingIdx = content.indexOf(headingPattern);
664
+ const headingPattern = `### ${existing.taskName} \u2014 ${existing.date} \u2014 Cycle ${existing.cycle}`;
665
+ let headingIdx = content.indexOf(headingPattern);
666
+ if (headingIdx === -1) {
667
+ const legacyPattern = `### ${existing.taskName} \u2014 ${existing.date} \u2014 Sprint ${existing.cycle}`;
668
+ headingIdx = content.indexOf(legacyPattern);
669
+ }
664
670
  if (headingIdx === -1) throw new Error(`Could not find existing build report for ${existing.taskId}`);
665
671
  const afterHeading = content.slice(headingIdx);
666
672
  const nextSeparator = afterHeading.indexOf("\n\n---\n");
@@ -687,9 +693,9 @@ function prependBuildReport(report, content) {
687
693
  }
688
694
 
689
695
  // src/parsers/metrics.ts
690
- var TABLE_HEADER = "| Timestamp | Tool | Duration (ms) | Input Tokens | Output Tokens | Cost ($) | Model | Sprint | Context |";
696
+ var TABLE_HEADER = "| Timestamp | Tool | Duration (ms) | Input Tokens | Output Tokens | Cost ($) | Model | Cycle | Context |";
691
697
  var TABLE_SEPARATOR = "|-----------|------|---------------|--------------|---------------|----------|-------|--------|---------|";
692
- var PREV_TABLE_HEADER = "| Timestamp | Tool | Duration (ms) | Input Tokens | Output Tokens | Cost ($) | Model | Sprint |";
698
+ var PREV_TABLE_HEADER = "| Timestamp | Tool | Duration (ms) | Input Tokens | Output Tokens | Cost ($) | Model | Cycle |";
693
699
  var LEGACY_TABLE_HEADER = "| Timestamp | Tool | Duration (ms) | Input Tokens | Output Tokens | Cost ($) | Model |";
694
700
  var SECTION_HEADING = "## Tool Call Metrics";
695
701
  var FILE_TEMPLATE = `# PAPI Metrics
@@ -728,8 +734,8 @@ function parseToolMetrics(content) {
728
734
  const outputTokens = cells[4] !== "-" ? parseInt(cells[4].replace(/,/g, ""), 10) : void 0;
729
735
  const cost = cells[5] !== "-" ? parseFloat(cells[5]) : void 0;
730
736
  const model = cells[6] !== "-" ? cells[6] : void 0;
731
- const sprintRaw = cells.length >= 8 && cells[7] !== "-" ? parseInt(cells[7], 10) : void 0;
732
- const sprintNumber = sprintRaw !== void 0 && !isNaN(sprintRaw) ? sprintRaw : void 0;
737
+ const cycleRaw = cells.length >= 8 && cells[7] !== "-" ? parseInt(cells[7], 10) : void 0;
738
+ const cycleNumber = cycleRaw !== void 0 && !isNaN(cycleRaw) ? cycleRaw : void 0;
733
739
  const contextRaw = cells.length >= 9 && cells[8] !== "-" ? parseInt(cells[8].replace(/,/g, ""), 10) : void 0;
734
740
  const contextBytes = contextRaw !== void 0 && !isNaN(contextRaw) ? contextRaw : void 0;
735
741
  const utilisationRaw = cells.length >= 10 && cells[9] !== "-" ? parseFloat(cells[9]) : void 0;
@@ -742,7 +748,7 @@ function parseToolMetrics(content) {
742
748
  ...outputTokens !== void 0 && !isNaN(outputTokens) ? { outputTokens } : {},
743
749
  ...cost !== void 0 && !isNaN(cost) ? { estimatedCostUsd: cost } : {},
744
750
  ...model ? { model } : {},
745
- ...sprintNumber !== void 0 ? { sprintNumber } : {},
751
+ ...cycleNumber !== void 0 ? { cycleNumber } : {},
746
752
  ...contextBytes !== void 0 ? { contextBytes } : {},
747
753
  ...contextUtilisation !== void 0 ? { contextUtilisation } : {}
748
754
  });
@@ -757,10 +763,10 @@ function serializeToolMetric(metric) {
757
763
  const outputTokens = metric.outputTokens !== void 0 ? formatNumber(metric.outputTokens) : "-";
758
764
  const cost = metric.estimatedCostUsd !== void 0 ? metric.estimatedCostUsd.toFixed(4) : "-";
759
765
  const model = metric.model ?? "-";
760
- const sprint = metric.sprintNumber !== void 0 ? String(metric.sprintNumber) : "-";
766
+ const cycle = metric.cycleNumber !== void 0 ? String(metric.cycleNumber) : "-";
761
767
  const context = metric.contextBytes !== void 0 ? formatNumber(metric.contextBytes) : "-";
762
768
  const utilisation = metric.contextUtilisation !== void 0 ? metric.contextUtilisation.toFixed(2) : "-";
763
- return `| ${metric.timestamp} | ${metric.tool} | ${formatNumber(metric.durationMs)} | ${inputTokens} | ${outputTokens} | ${cost} | ${model} | ${sprint} | ${context} | ${utilisation} |`;
769
+ return `| ${metric.timestamp} | ${metric.tool} | ${formatNumber(metric.durationMs)} | ${inputTokens} | ${outputTokens} | ${cost} | ${model} | ${cycle} | ${context} | ${utilisation} |`;
764
770
  }
765
771
  function appendToolMetricToContent(metric, content) {
766
772
  if (!content.trim()) {
@@ -791,8 +797,8 @@ function appendToolMetricToContent(metric, content) {
791
797
  const after = content.slice(costIdx);
792
798
  return before + "\n" + serializeToolMetric(metric) + "\n\n" + after;
793
799
  }
794
- function aggregateCostSummary(metrics, sprintNumber) {
795
- const filtered = sprintNumber !== void 0 ? metrics.filter((m) => m.sprintNumber === sprintNumber) : metrics;
800
+ function aggregateCostSummary(metrics, cycleNumber) {
801
+ const filtered = cycleNumber !== void 0 ? metrics.filter((m) => m.cycleNumber === cycleNumber) : metrics;
796
802
  let totalCostUsd = 0;
797
803
  let totalInputTokens = 0;
798
804
  let totalOutputTokens = 0;
@@ -824,7 +830,7 @@ function aggregateCostSummary(metrics, sprintNumber) {
824
830
  };
825
831
  }
826
832
  var COST_SECTION_HEADING = "## Cost Summary";
827
- var COST_TABLE_HEADER = "| Sprint | Date | Total Cost ($) | Input Tokens | Output Tokens | Calls |";
833
+ var COST_TABLE_HEADER = "| Cycle | Date | Total Cost ($) | Input Tokens | Output Tokens | Calls |";
828
834
  var COST_TABLE_SEPARATOR = "|--------|------|----------------|--------------|---------------|-------|";
829
835
  function parseCostSnapshots(content) {
830
836
  const lines = content.split("\n");
@@ -843,7 +849,7 @@ function parseCostSnapshots(content) {
843
849
  const cells = line.split("|").map((c) => c.trim()).filter(Boolean);
844
850
  if (cells.length < 6) continue;
845
851
  snapshots.push({
846
- sprint: parseInt(cells[0], 10),
852
+ cycle: parseInt(cells[0], 10),
847
853
  date: cells[1],
848
854
  totalCostUsd: parseFloat(cells[2]),
849
855
  totalInputTokens: parseInt(cells[3].replace(/,/g, ""), 10),
@@ -854,17 +860,17 @@ function parseCostSnapshots(content) {
854
860
  return snapshots;
855
861
  }
856
862
  function serializeCostSnapshot(snapshot) {
857
- return `| ${snapshot.sprint} | ${snapshot.date} | ${snapshot.totalCostUsd.toFixed(4)} | ${formatNumber(snapshot.totalInputTokens)} | ${formatNumber(snapshot.totalOutputTokens)} | ${formatNumber(snapshot.totalCalls)} |`;
863
+ return `| ${snapshot.cycle} | ${snapshot.date} | ${snapshot.totalCostUsd.toFixed(4)} | ${formatNumber(snapshot.totalInputTokens)} | ${formatNumber(snapshot.totalOutputTokens)} | ${formatNumber(snapshot.totalCalls)} |`;
858
864
  }
859
865
  function writeCostSnapshotToContent(snapshot, content) {
860
866
  if (!content.includes(COST_SECTION_HEADING)) {
861
867
  return content.trimEnd() + "\n\n" + COST_SECTION_HEADING + "\n\n" + COST_TABLE_HEADER + "\n" + COST_TABLE_SEPARATOR + "\n" + serializeCostSnapshot(snapshot) + "\n";
862
868
  }
863
869
  const lines = content.split("\n");
864
- const sprintPrefix = `| ${snapshot.sprint} |`;
870
+ const cyclePrefix = `| ${snapshot.cycle} |`;
865
871
  let replaced = false;
866
872
  for (let i = 0; i < lines.length; i++) {
867
- if (lines[i].startsWith(sprintPrefix)) {
873
+ if (lines[i].startsWith(cyclePrefix)) {
868
874
  lines[i] = serializeCostSnapshot(snapshot);
869
875
  replaced = true;
870
876
  break;
@@ -876,11 +882,11 @@ function writeCostSnapshotToContent(snapshot, content) {
876
882
  return content.trimEnd() + "\n" + serializeCostSnapshot(snapshot) + "\n";
877
883
  }
878
884
 
879
- // src/parsers/sprint-metrics.ts
880
- var FILE_HEADING = "# Sprint Methodology Metrics";
881
- var ACCURACY_HEADER = "| Sprint | Reports | Match Rate | MAE | Bias |";
885
+ // src/parsers/cycle-metrics.ts
886
+ var FILE_HEADING = "# Cycle Methodology Metrics";
887
+ var ACCURACY_HEADER = "| Cycle | Reports | Match Rate | MAE | Bias |";
882
888
  var ACCURACY_SEPARATOR = "|--------|---------|------------|-----|------|";
883
- var VELOCITY_HEADER = "| Sprint | Completed | Partial | Failed | Effort Points |";
889
+ var VELOCITY_HEADER = "| Cycle | Completed | Partial | Failed | Effort Points |";
884
890
  var VELOCITY_SEPARATOR = "|--------|-----------|---------|--------|---------------|";
885
891
  var EFFORT_SCALE = {
886
892
  XS: 1,
@@ -893,21 +899,21 @@ function effortOrdinal(effort) {
893
899
  const normalized = effort.trim().toUpperCase();
894
900
  return EFFORT_SCALE[normalized];
895
901
  }
896
- function calculateSprintMetrics(reports, currentSprint, window = 5) {
902
+ function calculateCycleMetrics(reports, currentCycle, window = 5) {
897
903
  const recentReports = reports.filter(
898
- (r) => r.sprint > currentSprint - window && r.sprint <= currentSprint
904
+ (r) => r.cycle > currentCycle - window && r.cycle <= currentCycle
899
905
  );
900
- const perSprint = /* @__PURE__ */ new Map();
906
+ const perCycle = /* @__PURE__ */ new Map();
901
907
  for (const r of recentReports) {
902
- const group = perSprint.get(r.sprint) ?? [];
908
+ const group = perCycle.get(r.cycle) ?? [];
903
909
  group.push(r);
904
- perSprint.set(r.sprint, group);
910
+ perCycle.set(r.cycle, group);
905
911
  }
906
912
  const accuracy = [];
907
913
  const velocity = [];
908
- const sortedSprints = [...perSprint.keys()].sort((a, b) => a - b);
909
- for (const sprint of sortedSprints) {
910
- const reps = perSprint.get(sprint);
914
+ const sortedCycles = [...perCycle.keys()].sort((a, b) => a - b);
915
+ for (const cycle of sortedCycles) {
916
+ const reps = perCycle.get(cycle);
911
917
  const deltas = [];
912
918
  for (const r of reps) {
913
919
  const actual = effortOrdinal(r.actualEffort);
@@ -918,7 +924,7 @@ function calculateSprintMetrics(reports, currentSprint, window = 5) {
918
924
  }
919
925
  if (deltas.length > 0) {
920
926
  accuracy.push({
921
- sprint,
927
+ cycle,
922
928
  reports: deltas.length,
923
929
  matchRate: Math.round(deltas.filter((d) => d === 0).length / deltas.length * 100),
924
930
  mae: Math.round(deltas.reduce((s, d) => s + Math.abs(d), 0) / deltas.length * 10) / 10,
@@ -926,7 +932,7 @@ function calculateSprintMetrics(reports, currentSprint, window = 5) {
926
932
  });
927
933
  }
928
934
  velocity.push({
929
- sprint,
935
+ cycle,
930
936
  completed: reps.filter((r) => r.completed === "Yes").length,
931
937
  partial: reps.filter((r) => r.completed === "Partial").length,
932
938
  failed: reps.filter((r) => r.completed === "No").length,
@@ -936,23 +942,23 @@ function calculateSprintMetrics(reports, currentSprint, window = 5) {
936
942
  return { accuracy, velocity };
937
943
  }
938
944
  function serializeAccuracyRow(a) {
939
- return `| ${a.sprint} | ${a.reports} | ${a.matchRate}% | ${a.mae} | ${a.bias >= 0 ? "+" : ""}${a.bias} |`;
945
+ return `| ${a.cycle} | ${a.reports} | ${a.matchRate}% | ${a.mae} | ${a.bias >= 0 ? "+" : ""}${a.bias} |`;
940
946
  }
941
947
  function serializeVelocityRow(v) {
942
- return `| ${v.sprint} | ${v.completed} | ${v.partial} | ${v.failed} | ${v.effortPoints} |`;
948
+ return `| ${v.cycle} | ${v.completed} | ${v.partial} | ${v.failed} | ${v.effortPoints} |`;
943
949
  }
944
950
  function serializeSnapshot(snapshot) {
945
951
  const lines = [];
946
- lines.push(`## Sprint ${snapshot.sprint} Snapshot \u2014 ${snapshot.date}`);
952
+ lines.push(`## Cycle ${snapshot.cycle} Snapshot \u2014 ${snapshot.date}`);
947
953
  lines.push("");
948
- lines.push("### Estimation Accuracy (last 5 sprints)");
954
+ lines.push("### Estimation Accuracy (last 5 cycles)");
949
955
  lines.push(ACCURACY_HEADER);
950
956
  lines.push(ACCURACY_SEPARATOR);
951
957
  for (const a of snapshot.accuracy) {
952
958
  lines.push(serializeAccuracyRow(a));
953
959
  }
954
960
  lines.push("");
955
- lines.push("### Sprint Velocity");
961
+ lines.push("### Cycle Velocity");
956
962
  lines.push(VELOCITY_HEADER);
957
963
  lines.push(VELOCITY_SEPARATOR);
958
964
  for (const v of snapshot.velocity) {
@@ -965,10 +971,13 @@ function appendSnapshotToContent(snapshot, content) {
965
971
  if (!content.trim()) {
966
972
  return FILE_HEADING + "\n\n" + block + "\n";
967
973
  }
968
- const marker = `## Sprint ${snapshot.sprint} Snapshot`;
969
- const markerIdx = content.indexOf(marker);
974
+ const marker = `## Cycle ${snapshot.cycle} Snapshot`;
975
+ const legacyMarker = `## Sprint ${snapshot.cycle} Snapshot`;
976
+ let markerIdx = content.indexOf(marker);
977
+ if (markerIdx === -1) markerIdx = content.indexOf(legacyMarker);
970
978
  if (markerIdx !== -1) {
971
- const nextSnapshotIdx = content.indexOf("\n## Sprint ", markerIdx + marker.length);
979
+ let nextSnapshotIdx = content.indexOf("\n## Cycle ", markerIdx + marker.length);
980
+ if (nextSnapshotIdx === -1) nextSnapshotIdx = content.indexOf("\n## Sprint ", markerIdx + marker.length);
972
981
  const before = content.slice(0, markerIdx).trimEnd();
973
982
  const after = nextSnapshotIdx !== -1 ? content.slice(nextSnapshotIdx) : "";
974
983
  return before + "\n\n" + block + (after ? after : "\n");
@@ -978,12 +987,12 @@ function appendSnapshotToContent(snapshot, content) {
978
987
  function parseSnapshots(content) {
979
988
  if (!content.trim()) return [];
980
989
  const snapshots = [];
981
- const headerRegex = /^## Sprint (\d+) Snapshot — (\S+)/gm;
990
+ const headerRegex = /^## (?:Cycle|Sprint) (\d+) Snapshot — (\S+)/gm;
982
991
  let match;
983
992
  const headers = [];
984
993
  while ((match = headerRegex.exec(content)) !== null) {
985
994
  headers.push({
986
- sprint: parseInt(match[1], 10),
995
+ cycle: parseInt(match[1], 10),
987
996
  date: match[2],
988
997
  index: match.index
989
998
  });
@@ -995,7 +1004,7 @@ function parseSnapshots(content) {
995
1004
  const accuracy = parseAccuracyTable(block);
996
1005
  const velocity = parseVelocityTable(block);
997
1006
  snapshots.push({
998
- sprint: headers[i].sprint,
1007
+ cycle: headers[i].cycle,
999
1008
  date: headers[i].date,
1000
1009
  accuracy,
1001
1010
  velocity
@@ -1011,22 +1020,22 @@ function isRealFinding(text) {
1011
1020
  const trimmed = text.trim();
1012
1021
  return trimmed.length > 0 && !NONE_PATTERN.test(trimmed);
1013
1022
  }
1014
- async function detectBuildPatterns(reports, currentSprint, window = 5, clusterer) {
1023
+ async function detectBuildPatterns(reports, currentCycle, window = 5, clusterer) {
1015
1024
  const recentReports = reports.filter(
1016
- (r) => r.sprint > currentSprint - window && r.sprint <= currentSprint
1025
+ (r) => r.cycle > currentCycle - window && r.cycle <= currentCycle
1017
1026
  );
1018
1027
  let recurringSurprises;
1019
1028
  const realSurprises = recentReports.filter((r) => isRealFinding(r.surprises));
1020
1029
  if (clusterer && realSurprises.length >= 2) {
1021
1030
  const entries = realSurprises.map((r) => ({
1022
1031
  text: r.surprises.trim(),
1023
- source: String(r.sprint)
1032
+ source: String(r.cycle)
1024
1033
  }));
1025
1034
  const clusters = await clusterer(entries);
1026
1035
  recurringSurprises = clusters.filter((c) => c.entries.length >= 2).map((c) => ({
1027
1036
  text: c.theme,
1028
1037
  count: c.entries.length,
1029
- sprints: [...new Set(c.entries.map((e) => parseInt(e.source, 10)))].sort((a, b) => a - b)
1038
+ cycles: [...new Set(c.entries.map((e) => parseInt(e.source, 10)))].sort((a, b) => a - b)
1030
1039
  }));
1031
1040
  recurringSurprises.sort((a, b) => b.count - a.count);
1032
1041
  } else {
@@ -1036,9 +1045,9 @@ async function detectBuildPatterns(reports, currentSprint, window = 5, clusterer
1036
1045
  const existing = surpriseMap.get(key);
1037
1046
  if (existing) {
1038
1047
  existing.count += 1;
1039
- existing.sprints.add(r.sprint);
1048
+ existing.cycles.add(r.cycle);
1040
1049
  } else {
1041
- surpriseMap.set(key, { original: r.surprises.trim(), count: 1, sprints: /* @__PURE__ */ new Set([r.sprint]) });
1050
+ surpriseMap.set(key, { original: r.surprises.trim(), count: 1, cycles: /* @__PURE__ */ new Set([r.cycle]) });
1042
1051
  }
1043
1052
  }
1044
1053
  recurringSurprises = [];
@@ -1047,7 +1056,7 @@ async function detectBuildPatterns(reports, currentSprint, window = 5, clusterer
1047
1056
  recurringSurprises.push({
1048
1057
  text: entry.original,
1049
1058
  count: entry.count,
1050
- sprints: [...entry.sprints].sort((a, b) => a - b)
1059
+ cycles: [...entry.cycles].sort((a, b) => a - b)
1051
1060
  });
1052
1061
  }
1053
1062
  }
@@ -1117,7 +1126,7 @@ function parseAccuracyTable(block) {
1117
1126
  const cells = line.split("|").map((c) => c.trim()).filter(Boolean);
1118
1127
  if (cells.length < 5) continue;
1119
1128
  rows.push({
1120
- sprint: parseInt(cells[0], 10),
1129
+ cycle: parseInt(cells[0], 10),
1121
1130
  reports: parseInt(cells[1], 10),
1122
1131
  matchRate: parseInt(cells[2].replace("%", ""), 10),
1123
1132
  mae: parseFloat(cells[3]),
@@ -1143,7 +1152,7 @@ function parseVelocityTable(block) {
1143
1152
  const cells = line.split("|").map((c) => c.trim()).filter(Boolean);
1144
1153
  if (cells.length < 5) continue;
1145
1154
  rows.push({
1146
- sprint: parseInt(cells[0], 10),
1155
+ cycle: parseInt(cells[0], 10),
1147
1156
  completed: parseInt(cells[1], 10),
1148
1157
  partial: parseInt(cells[2], 10),
1149
1158
  failed: parseInt(cells[3], 10),
@@ -1178,12 +1187,12 @@ function parseReviews(content) {
1178
1187
  const verdictRaw = parseField2(block, "Verdict");
1179
1188
  if (!VALID_VERDICTS.has(verdictRaw)) return null;
1180
1189
  const verdict = verdictRaw;
1181
- const sprint = parseInt(parseField2(block, "Sprint"), 10);
1182
- if (isNaN(sprint)) return null;
1190
+ const cycle = parseInt(parseField2(block, "Cycle"), 10);
1191
+ if (isNaN(cycle)) return null;
1183
1192
  const comments = parseField2(block, "Comments");
1184
1193
  const uuidRaw = parseField2(block, "UUID");
1185
1194
  const displayIdRaw = parseField2(block, "Display ID");
1186
- const review = { uuid: uuidRaw ?? randomUUID5(), ...displayIdRaw ? { displayId: displayIdRaw } : {}, taskId, stage, reviewer, verdict, sprint, date, comments };
1195
+ const review = { uuid: uuidRaw ?? randomUUID5(), ...displayIdRaw ? { displayId: displayIdRaw } : {}, taskId, stage, reviewer, verdict, cycle, date, comments };
1187
1196
  const handoffRevRaw = parseField2(block, "Handoff Revision");
1188
1197
  if (handoffRevRaw) {
1189
1198
  const parsed = parseInt(handoffRevRaw, 10);
@@ -1209,11 +1218,22 @@ function serializeReview(review) {
1209
1218
  lines.push(
1210
1219
  `- **Reviewer:** ${review.reviewer}`,
1211
1220
  `- **Verdict:** ${review.verdict}`,
1212
- `- **Sprint:** ${review.sprint}`,
1221
+ `- **Cycle:** ${review.cycle}`,
1213
1222
  `- **Comments:** ${review.comments}`
1214
1223
  );
1215
1224
  if (review.handoffRevision !== void 0) lines.push(`- **Handoff Revision:** ${review.handoffRevision}`);
1216
1225
  if (review.buildCommitSha) lines.push(`- **Build Commit SHA:** ${review.buildCommitSha}`);
1226
+ if (review.autoReview) {
1227
+ lines.push("", `#### Auto-Review (${review.autoReview.verdict})`);
1228
+ lines.push(`> ${review.autoReview.summary}`);
1229
+ if (review.autoReview.findings.length > 0) {
1230
+ lines.push("");
1231
+ for (const f of review.autoReview.findings) {
1232
+ const loc = f.file ? f.line ? `${f.file}:${f.line}` : f.file : "";
1233
+ lines.push(`- \`${f.severity}\`${loc ? ` ${loc}` : ""}: ${f.message}`);
1234
+ }
1235
+ }
1236
+ }
1217
1237
  return lines.join("\n");
1218
1238
  }
1219
1239
  function prependReview(review, content) {
@@ -1320,126 +1340,15 @@ ${PHASES_END}`;
1320
1340
  return content.trimEnd() + "\n\n" + newSection + "\n";
1321
1341
  }
1322
1342
 
1323
- // src/parsers/features.ts
1324
- var VALID_STATUSES2 = /* @__PURE__ */ new Set(["Not Started", "In Progress", "Done", "Deferred"]);
1325
- var FEATURES_START = "<!-- FEATURES:START -->";
1326
- var FEATURES_END = "<!-- FEATURES:END -->";
1327
- function parseFeatures(content) {
1328
- const startIdx = content.indexOf(FEATURES_START);
1329
- const endIdx = content.indexOf(FEATURES_END);
1330
- if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) return [];
1331
- const section = content.slice(startIdx + FEATURES_START.length, endIdx);
1332
- const yamlMatch = section.match(/```yaml\s*\n([\s\S]*?)```/);
1333
- if (!yamlMatch) return [];
1334
- const yamlBody = yamlMatch[1];
1335
- const features = [];
1336
- const blocks = yamlBody.split(/^(?=\s*- id:)/m).filter((b) => b.trim());
1337
- for (const block of blocks) {
1338
- const feature = parseFeatureBlock(block);
1339
- if (feature) features.push(feature);
1340
- }
1341
- return features.sort((a, b) => a.roadmapPosition - b.roadmapPosition);
1342
- }
1343
- function parseYamlField2(block, field) {
1344
- const pattern = new RegExp(`^\\s*${field}:\\s*(.+)$`, "m");
1345
- const match = block.match(pattern);
1346
- if (!match) return "";
1347
- let value = match[1].trim();
1348
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
1349
- value = value.slice(1, -1);
1350
- }
1351
- return value;
1352
- }
1353
- function parseOptionalYamlField(block, field) {
1354
- const value = parseYamlField2(block, field);
1355
- return value || void 0;
1356
- }
1357
- function parseFeatureBlock(block) {
1358
- const idMatch = block.match(/^\s*- id:\s*(.+)$/m);
1359
- if (!idMatch) return null;
1360
- let id = idMatch[1].trim();
1361
- if (id.startsWith('"') && id.endsWith('"') || id.startsWith("'") && id.endsWith("'")) {
1362
- id = id.slice(1, -1);
1363
- }
1364
- const displayId = parseYamlField2(block, "displayId");
1365
- const slug = parseYamlField2(block, "slug");
1366
- const name = parseYamlField2(block, "name");
1367
- const description = parseYamlField2(block, "description");
1368
- const statusRaw = parseYamlField2(block, "status");
1369
- const roadmapPositionRaw = parseYamlField2(block, "roadmapPosition");
1370
- const createdAt = parseYamlField2(block, "createdAt");
1371
- const createdSprintRaw = parseYamlField2(block, "createdSprint");
1372
- if (!displayId || !slug || !name || !statusRaw || !roadmapPositionRaw || !createdAt || !createdSprintRaw) return null;
1373
- const status = statusRaw;
1374
- if (!VALID_STATUSES2.has(status)) return null;
1375
- const roadmapPosition = parseInt(roadmapPositionRaw, 10);
1376
- if (isNaN(roadmapPosition)) return null;
1377
- const createdSprint = parseInt(createdSprintRaw, 10);
1378
- if (isNaN(createdSprint)) return null;
1379
- const completedAt = parseOptionalYamlField(block, "completedAt");
1380
- const commitDate = parseOptionalYamlField(block, "commitDate");
1381
- const completedSprintRaw = parseOptionalYamlField(block, "completedSprint");
1382
- const completedSprint = completedSprintRaw ? parseInt(completedSprintRaw, 10) : void 0;
1383
- return {
1384
- id,
1385
- displayId,
1386
- slug,
1387
- name,
1388
- description,
1389
- status,
1390
- roadmapPosition,
1391
- createdAt,
1392
- completedAt,
1393
- commitDate,
1394
- createdSprint,
1395
- completedSprint: completedSprint !== void 0 && !isNaN(completedSprint) ? completedSprint : void 0
1396
- };
1397
- }
1398
- function serializeFeatures(features) {
1399
- const sorted = [...features].sort((a, b) => a.roadmapPosition - b.roadmapPosition);
1400
- const yamlLines = ["features:"];
1401
- for (const f of sorted) {
1402
- yamlLines.push(` - id: "${f.id}"`);
1403
- yamlLines.push(` displayId: "${f.displayId}"`);
1404
- yamlLines.push(` slug: "${f.slug}"`);
1405
- yamlLines.push(` name: "${f.name}"`);
1406
- yamlLines.push(` description: "${f.description}"`);
1407
- yamlLines.push(` status: "${f.status}"`);
1408
- yamlLines.push(` roadmapPosition: ${f.roadmapPosition}`);
1409
- yamlLines.push(` createdAt: "${f.createdAt}"`);
1410
- if (f.completedAt) yamlLines.push(` completedAt: "${f.completedAt}"`);
1411
- if (f.commitDate) yamlLines.push(` commitDate: "${f.commitDate}"`);
1412
- yamlLines.push(` createdSprint: ${f.createdSprint}`);
1413
- if (f.completedSprint !== void 0) yamlLines.push(` completedSprint: ${f.completedSprint}`);
1414
- }
1415
- return yamlLines.join("\n");
1416
- }
1417
- function writeFeaturesToContent(features, content) {
1418
- const yaml4 = serializeFeatures(features);
1419
- const newSection = `${FEATURES_START}
1420
-
1421
- \`\`\`yaml
1422
- ${yaml4}
1423
- \`\`\`
1424
-
1425
- ${FEATURES_END}`;
1426
- const startIdx = content.indexOf(FEATURES_START);
1427
- const endIdx = content.indexOf(FEATURES_END);
1428
- if (startIdx !== -1 && endIdx !== -1) {
1429
- return content.slice(0, startIdx) + newSection + content.slice(endIdx + FEATURES_END.length);
1430
- }
1431
- return content.trimEnd() + "\n\n" + newSection + "\n";
1432
- }
1433
-
1434
- // src/parsers/sprints.ts
1343
+ // src/parsers/cycles.ts
1435
1344
  import yaml2 from "js-yaml";
1436
1345
  var YAML_MARKER2 = "<!-- PAPI-ADAPTER: parse the yaml block below -->";
1437
1346
  var YAML_START2 = "<!-- PAPI-YAML-START -->";
1438
1347
  var YAML_END2 = "<!-- PAPI-YAML-END -->";
1439
- var VALID_STATUSES3 = /* @__PURE__ */ new Set(["planning", "active", "complete"]);
1440
- function toSprint(raw) {
1441
- if (!VALID_STATUSES3.has(raw.status)) return null;
1442
- const sprint = {
1348
+ var VALID_STATUSES2 = /* @__PURE__ */ new Set(["planning", "active", "complete"]);
1349
+ function toCycle(raw) {
1350
+ if (!VALID_STATUSES2.has(raw.status)) return null;
1351
+ const cycle = {
1443
1352
  id: raw.id,
1444
1353
  number: raw.number,
1445
1354
  status: raw.status,
@@ -1448,71 +1357,71 @@ function toSprint(raw) {
1448
1357
  boardHealth: raw.board_health ?? "",
1449
1358
  taskIds: raw.task_ids ?? []
1450
1359
  };
1451
- if (raw.end_date) sprint.endDate = raw.end_date;
1452
- return sprint;
1360
+ if (raw.end_date) cycle.endDate = raw.end_date;
1361
+ return cycle;
1453
1362
  }
1454
- function fromSprint(sprint) {
1363
+ function fromCycle(cycle) {
1455
1364
  const raw = {
1456
- id: sprint.id,
1457
- number: sprint.number,
1458
- status: sprint.status,
1459
- start_date: sprint.startDate,
1460
- goals: sprint.goals,
1461
- board_health: sprint.boardHealth,
1462
- task_ids: sprint.taskIds
1365
+ id: cycle.id,
1366
+ number: cycle.number,
1367
+ status: cycle.status,
1368
+ start_date: cycle.startDate,
1369
+ goals: cycle.goals,
1370
+ board_health: cycle.boardHealth,
1371
+ task_ids: cycle.taskIds
1463
1372
  };
1464
- if (sprint.endDate) raw.end_date = sprint.endDate;
1373
+ if (cycle.endDate) raw.end_date = cycle.endDate;
1465
1374
  return raw;
1466
1375
  }
1467
1376
  function extractYamlBlock2(content) {
1468
1377
  const markerIdx = content.indexOf(YAML_MARKER2);
1469
- if (markerIdx === -1) throw new Error("PAPI-ADAPTER marker not found in SPRINTS.md");
1378
+ if (markerIdx === -1) throw new Error("PAPI-ADAPTER marker not found in CYCLES.md");
1470
1379
  const afterMarker = content.slice(markerIdx + YAML_MARKER2.length);
1471
1380
  const startIdx = afterMarker.indexOf(YAML_START2);
1472
- if (startIdx === -1) throw new Error("PAPI-YAML-START marker not found in SPRINTS.md");
1381
+ if (startIdx === -1) throw new Error("PAPI-YAML-START marker not found in CYCLES.md");
1473
1382
  const yamlStart = startIdx + YAML_START2.length;
1474
1383
  const endIdx = afterMarker.indexOf(YAML_END2, yamlStart);
1475
- if (endIdx === -1) throw new Error("PAPI-YAML-END marker not found in SPRINTS.md");
1384
+ if (endIdx === -1) throw new Error("PAPI-YAML-END marker not found in CYCLES.md");
1476
1385
  return afterMarker.slice(yamlStart, endIdx);
1477
1386
  }
1478
- function parseSprints(content) {
1387
+ function parseCycles(content) {
1479
1388
  if (!content.trim()) return [];
1480
1389
  const yamlText = extractYamlBlock2(content);
1481
1390
  const data = yaml2.load(yamlText);
1482
- return (data.sprints ?? []).map(toSprint).filter((s) => s !== null);
1391
+ return (data.cycles ?? []).map(toCycle).filter((s) => s !== null);
1483
1392
  }
1484
- function serializeSprints(sprints, content) {
1485
- const raw = sprints.map(fromSprint);
1486
- const yamlStr = yaml2.dump({ sprints: raw }, { lineWidth: 120, quotingType: '"' });
1393
+ function serializeCycles(cycles, content) {
1394
+ const raw = cycles.map(fromCycle);
1395
+ const yamlStr = yaml2.dump({ cycles: raw }, { lineWidth: 120, quotingType: '"' });
1487
1396
  const markerIdx = content.indexOf(YAML_MARKER2);
1488
- if (markerIdx === -1) throw new Error("PAPI-ADAPTER marker not found in SPRINTS.md");
1397
+ if (markerIdx === -1) throw new Error("PAPI-ADAPTER marker not found in CYCLES.md");
1489
1398
  const afterMarker = content.slice(markerIdx + YAML_MARKER2.length);
1490
1399
  const startIdx = afterMarker.indexOf(YAML_START2);
1491
- if (startIdx === -1) throw new Error("PAPI-YAML-START marker not found in SPRINTS.md");
1400
+ if (startIdx === -1) throw new Error("PAPI-YAML-START marker not found in CYCLES.md");
1492
1401
  const absStart = markerIdx + YAML_MARKER2.length + startIdx;
1493
1402
  const endIdx = afterMarker.indexOf(YAML_END2, startIdx);
1494
- if (endIdx === -1) throw new Error("PAPI-YAML-END marker not found in SPRINTS.md");
1403
+ if (endIdx === -1) throw new Error("PAPI-YAML-END marker not found in CYCLES.md");
1495
1404
  const absEnd = markerIdx + YAML_MARKER2.length + endIdx + YAML_END2.length;
1496
1405
  return content.slice(0, absStart) + YAML_START2 + "\n" + yamlStr + YAML_END2 + content.slice(absEnd);
1497
1406
  }
1498
- function serializeSprint(sprint) {
1499
- const raw = fromSprint(sprint);
1407
+ function serializeCycle(cycle) {
1408
+ const raw = fromCycle(cycle);
1500
1409
  return yaml2.dump(raw, { lineWidth: 120, quotingType: '"' }).trim();
1501
1410
  }
1502
- function prependSprint(sprint, content) {
1411
+ function prependCycle(cycle, content) {
1503
1412
  if (!content.trim()) {
1504
- const header = `# Sprints
1413
+ const header = `# Cycles
1505
1414
 
1506
1415
  <!-- PAPI-ADAPTER: parse the yaml block below -->
1507
1416
 
1508
1417
  `;
1509
- const raw = fromSprint(sprint);
1510
- const yamlStr = yaml2.dump({ sprints: [raw] }, { lineWidth: 120, quotingType: '"' });
1418
+ const raw = fromCycle(cycle);
1419
+ const yamlStr = yaml2.dump({ cycles: [raw] }, { lineWidth: 120, quotingType: '"' });
1511
1420
  return header + YAML_START2 + "\n" + yamlStr + YAML_END2 + "\n";
1512
1421
  }
1513
- const existing = parseSprints(content);
1514
- const merged = [sprint, ...existing];
1515
- return serializeSprints(merged, content);
1422
+ const existing = parseCycles(content);
1423
+ const merged = [cycle, ...existing];
1424
+ return serializeCycles(merged, content);
1516
1425
  }
1517
1426
 
1518
1427
  // src/parsers/registries.ts
@@ -1588,18 +1497,18 @@ var MdFileAdapter = class {
1588
1497
  await writeFile(this.path(file), content, "utf-8");
1589
1498
  }
1590
1499
  // --- Planning Log ---
1591
- /** Parse the full planning context into structured sections (reads from PLANNING_LOG.md + ACTIVE_DECISIONS.md + SPRINT_LOG.md). */
1500
+ /** Parse the full planning context into structured sections (reads from PLANNING_LOG.md + ACTIVE_DECISIONS.md + CYCLE_LOG.md). */
1592
1501
  async readPlanningLog() {
1593
- const [planningContent, activeDecisionsContent, sprintLogContent] = await Promise.all([
1502
+ const [planningContent, activeDecisionsContent, cycleLogContent] = await Promise.all([
1594
1503
  this.read("PLANNING_LOG.md"),
1595
1504
  this.readOptional("ACTIVE_DECISIONS.md"),
1596
1505
  this.readOptional("SPRINT_LOG.md")
1597
1506
  ]);
1598
- return parsePlanningLog(planningContent, activeDecisionsContent, sprintLogContent);
1507
+ return parsePlanningLog(planningContent, activeDecisionsContent, cycleLogContent);
1599
1508
  }
1600
- /** Read the Sprint Health table from PLANNING_LOG.md. */
1601
- async getSprintHealth() {
1602
- return parseSprintHealth(await this.read("PLANNING_LOG.md"));
1509
+ /** Read the Cycle Health table from PLANNING_LOG.md. */
1510
+ async getCycleHealth() {
1511
+ return parseCycleHealth(await this.read("PLANNING_LOG.md"));
1603
1512
  }
1604
1513
  /** Read all Active Decisions from ACTIVE_DECISIONS.md. */
1605
1514
  async getActiveDecisions() {
@@ -1607,58 +1516,58 @@ var MdFileAdapter = class {
1607
1516
  if (!content) return [];
1608
1517
  return parseActiveDecisions(content);
1609
1518
  }
1610
- /** Read sprint log entries (newest first), optionally limited to {@link limit} entries. */
1611
- async getSprintLog(limit) {
1612
- return parseSprintLog(await this.read("SPRINT_LOG.md"), limit);
1519
+ /** Read cycle log entries (newest first), optionally limited to {@link limit} entries. */
1520
+ async getCycleLog(limit) {
1521
+ return parseCycleLog(await this.read("SPRINT_LOG.md"), limit);
1613
1522
  }
1614
- async getSprintLogSince(sprintNumber) {
1615
- const log = await this.getSprintLog();
1616
- return log.filter((entry) => entry.sprintNumber >= sprintNumber);
1523
+ async getCycleLogSince(cycleNumber) {
1524
+ const log = await this.getCycleLog();
1525
+ return log.filter((entry) => entry.cycleNumber >= cycleNumber);
1617
1526
  }
1618
- /** Merge partial updates into the Sprint Health table and write back. */
1619
- async setSprintHealth(updates) {
1527
+ /** Merge partial updates into the Cycle Health table and write back. */
1528
+ async setCycleHealth(updates) {
1620
1529
  const content = await this.read("PLANNING_LOG.md");
1621
- const current = parseSprintHealth(content);
1530
+ const current = parseCycleHealth(content);
1622
1531
  const updated = { ...current, ...updates };
1623
- await this.write("PLANNING_LOG.md", serializeSprintHealth(updated, content));
1532
+ await this.write("PLANNING_LOG.md", serializeCycleHealth(updated, content));
1624
1533
  }
1625
- /** Prepend a new sprint log entry at the top of the Sprint Log section. */
1626
- async writeSprintLogEntry(entry) {
1534
+ /** Prepend a new cycle log entry at the top of the Cycle Log section. */
1535
+ async writeCycleLogEntry(entry) {
1627
1536
  if (!entry.uuid) {
1628
1537
  entry = { ...entry, uuid: randomUUID6() };
1629
1538
  }
1630
1539
  const content = await this.read("SPRINT_LOG.md");
1631
- await this.write("SPRINT_LOG.md", prependSprintLogEntry(entry, content));
1540
+ await this.write("SPRINT_LOG.md", prependCycleLogEntry(entry, content));
1632
1541
  }
1633
- /** Write a strategy review — for md adapter, delegates to sprint log. */
1542
+ /** Write a strategy review — for md adapter, delegates to cycle log. */
1634
1543
  async writeStrategyReview(review) {
1635
- await this.writeSprintLogEntry({
1544
+ await this.writeCycleLogEntry({
1636
1545
  uuid: randomUUID6(),
1637
- sprintNumber: review.sprintNumber,
1546
+ cycleNumber: review.cycleNumber,
1638
1547
  title: review.title,
1639
1548
  content: review.content,
1640
1549
  notes: review.notes
1641
1550
  });
1642
1551
  }
1643
- /** Get the sprint number of the last strategy review. */
1644
- async getLastStrategyReviewSprint() {
1645
- const log = await this.getSprintLog();
1552
+ /** Get the cycle number of the last strategy review. */
1553
+ async getLastStrategyReviewCycle() {
1554
+ const log = await this.getCycleLog();
1646
1555
  const entry = log.find(
1647
1556
  (e) => /strategy.*review|strategic.*shift/i.test(e.title)
1648
1557
  );
1649
- return entry?.sprintNumber ?? 0;
1558
+ return entry?.cycleNumber ?? 0;
1650
1559
  }
1651
- /** Get strategy reviews — md adapter returns empty (reviews live in sprint log). */
1652
- async getStrategyReviews(_limit) {
1560
+ /** Get strategy reviews — md adapter returns empty (reviews live in cycle log). */
1561
+ async getStrategyReviews(_limit, _includeFullAnalysis) {
1653
1562
  return [];
1654
1563
  }
1655
1564
  /** Update or insert an Active Decision block by ID. */
1656
- async updateActiveDecision(id, body, sprintNumber) {
1565
+ async updateActiveDecision(id, body, cycleNumber) {
1657
1566
  const content = await this.readOptional("ACTIVE_DECISIONS.md") || "## Active Decisions\n\n";
1658
- await this.write("ACTIVE_DECISIONS.md", updateActiveDecisionInContent(id, body, content, sprintNumber));
1567
+ await this.write("ACTIVE_DECISIONS.md", updateActiveDecisionInContent(id, body, content, cycleNumber));
1659
1568
  }
1660
- // --- Sprint Board ---
1661
- /** Query the sprint board, optionally filtering by status/priority/phase/etc. */
1569
+ // --- Cycle Board ---
1570
+ /** Query the cycle board, optionally filtering by status/priority/phase/etc. */
1662
1571
  async queryBoard(options) {
1663
1572
  const tasks = parseBoard(await this.read("SPRINT_BOARD.md"));
1664
1573
  return options ? filterTasks(tasks, options) : tasks;
@@ -1714,7 +1623,7 @@ var MdFileAdapter = class {
1714
1623
  const archivedTasks = archiveContent ? parseBoard(archiveContent) : [];
1715
1624
  await this.warnInvalidPhase(task.phase);
1716
1625
  await this.warnInvalidModule(task.module);
1717
- await this.warnInvalidEpic(task.epic);
1626
+ if (task.epic) await this.warnInvalidEpic(task.epic);
1718
1627
  if (task.dependsOn) {
1719
1628
  const allTaskIds = new Set([...tasks, ...archivedTasks].map((t) => t.id));
1720
1629
  const depIds = task.dependsOn.split(",").map((s) => s.trim()).filter(Boolean);
@@ -1783,6 +1692,8 @@ var MdFileAdapter = class {
1783
1692
  async updateTaskStatus(id, status) {
1784
1693
  return this.updateTask(id, { status });
1785
1694
  }
1695
+ async recordTransition(_taskId, _fromStatus, _toStatus, _changedBy) {
1696
+ }
1786
1697
  // --- Build Reports ---
1787
1698
  /** Insert a new build report at the top of BUILD_REPORTS.md. */
1788
1699
  async appendBuildReport(report) {
@@ -1810,10 +1721,10 @@ var MdFileAdapter = class {
1810
1721
  const reports = parseBuildReports(await this.read("BUILD_REPORTS.md"));
1811
1722
  return reports.slice(0, count);
1812
1723
  }
1813
- /** Return all build reports from sprints >= {@link sprintNumber}. */
1814
- async getBuildReportsSince(sprintNumber) {
1724
+ /** Return all build reports from cycles >= {@link cycleNumber}. */
1725
+ async getBuildReportsSince(cycleNumber) {
1815
1726
  const reports = parseBuildReports(await this.read("BUILD_REPORTS.md"));
1816
- return reports.filter((r) => r.sprint >= sprintNumber);
1727
+ return reports.filter((r) => r.cycle >= cycleNumber);
1817
1728
  }
1818
1729
  // --- Human Reviews ---
1819
1730
  /** Return recent human reviews from REVIEWS.md (newest first), optionally limited to {@link count}. */
@@ -1845,10 +1756,10 @@ var MdFileAdapter = class {
1845
1756
  await this.write("REVIEWS.md", prependReview(review, content));
1846
1757
  }
1847
1758
  // --- Compression ---
1848
- /** Compress old sprint log entries below {@link threshold} into a summary block. */
1849
- async compressSprintLog(threshold, summary) {
1759
+ /** Compress old cycle log entries below {@link threshold} into a summary block. */
1760
+ async compressCycleLog(threshold, summary) {
1850
1761
  const content = await this.read("SPRINT_LOG.md");
1851
- await this.write("SPRINT_LOG.md", compressSprintLogInContent(content, threshold, summary));
1762
+ await this.write("SPRINT_LOG.md", compressCycleLogInContent(content, threshold, summary));
1852
1763
  }
1853
1764
  /** Compress old build reports below {@link threshold} into a summary block. */
1854
1765
  async compressBuildReports(threshold, summary) {
@@ -1870,17 +1781,17 @@ var MdFileAdapter = class {
1870
1781
  const { buildHandoff, buildReport, ...rest } = task;
1871
1782
  return rest;
1872
1783
  }
1873
- /** Append tasks to ARCHIVE_SPRINT_BOARD.md, stripping heavy fields and deduplicating by ID. */
1784
+ /** Append tasks to ARCHIVE_CYCLE_BOARD.md, stripping heavy fields and deduplicating by ID. */
1874
1785
  async appendToArchive(tasks) {
1875
1786
  const existing = await this.readOptional("ARCHIVE_SPRINT_BOARD.md");
1876
- const archiveContent = existing || "# PAPI Sprint Board \u2014 Archive\n\n<!-- PAPI-ADAPTER: parse the yaml block below -->\n\n<!-- PAPI-YAML-START -->\ntasks: []\n<!-- PAPI-YAML-END -->\n";
1787
+ const archiveContent = existing || "# PAPI Cycle Board \u2014 Archive\n\n<!-- PAPI-ADAPTER: parse the yaml block below -->\n\n<!-- PAPI-YAML-START -->\ntasks: []\n<!-- PAPI-YAML-END -->\n";
1877
1788
  const existingArchived = parseBoard(archiveContent);
1878
1789
  const existingIds = new Set(existingArchived.map((t) => t.id));
1879
1790
  const newArchive = tasks.filter((t) => !existingIds.has(t.id)).map((t) => this.stripHeavyFields(t));
1880
1791
  const merged = [...existingArchived, ...newArchive];
1881
1792
  await this.write("ARCHIVE_SPRINT_BOARD.md", serializeBoard(merged, archiveContent));
1882
1793
  }
1883
- /** Archive tasks matching phases and/or statuses to ARCHIVE_SPRINT_BOARD.md and remove them from active board. */
1794
+ /** Archive tasks matching phases and/or statuses to ARCHIVE_CYCLE_BOARD.md and remove them from active board. */
1884
1795
  async archiveTasks(phases, statuses) {
1885
1796
  const content = await this.read("SPRINT_BOARD.md");
1886
1797
  const tasks = parseBoard(content);
@@ -1916,6 +1827,11 @@ var MdFileAdapter = class {
1916
1827
  async updateProductBrief(content) {
1917
1828
  await this.write("PRODUCT_BRIEF.md", content);
1918
1829
  }
1830
+ async readDiscoveryCanvas() {
1831
+ return {};
1832
+ }
1833
+ async updateDiscoveryCanvas(_canvas) {
1834
+ }
1919
1835
  /** Read all phases from PHASES.md (falls back to PRODUCT_BRIEF.md for migration). */
1920
1836
  async readPhases() {
1921
1837
  const phasesContent = await this.readOptional("PHASES.md");
@@ -1944,59 +1860,6 @@ ${PHASES_END2}`;
1944
1860
  } else {
1945
1861
  await this.write("PHASES.md", `# Phases
1946
1862
 
1947
- ${newSection}
1948
- `);
1949
- }
1950
- }
1951
- // --- Features ---
1952
- /** Read all features from FEATURES.md. */
1953
- async readFeatures() {
1954
- const content = await this.readOptional("FEATURES.md");
1955
- return content ? parseFeatures(content) : [];
1956
- }
1957
- /** Get a single feature by UUID. */
1958
- async getFeature(id) {
1959
- const features = await this.readFeatures();
1960
- return features.find((f) => f.id === id) ?? null;
1961
- }
1962
- /** Create a new feature and append it to FEATURES.md. */
1963
- async createFeature(feature) {
1964
- const features = await this.readFeatures();
1965
- const id = randomUUID6();
1966
- const newFeature = { id, ...feature };
1967
- features.push(newFeature);
1968
- await this.writeFeatures(features);
1969
- return newFeature;
1970
- }
1971
- /** Update an existing feature by UUID. */
1972
- async updateFeature(id, updates) {
1973
- const features = await this.readFeatures();
1974
- const idx = features.findIndex((f) => f.id === id);
1975
- if (idx === -1) throw new Error(`Feature not found: ${id}`);
1976
- features[idx] = { ...features[idx], ...updates };
1977
- await this.writeFeatures(features);
1978
- }
1979
- /** Write all features to FEATURES.md. */
1980
- async writeFeatures(features) {
1981
- const content = await this.readOptional("FEATURES.md");
1982
- const existing = content || "";
1983
- const yaml4 = serializeFeatures(features);
1984
- const FEATURES_START2 = "<!-- FEATURES:START -->";
1985
- const FEATURES_END2 = "<!-- FEATURES:END -->";
1986
- const newSection = `${FEATURES_START2}
1987
-
1988
- \`\`\`yaml
1989
- ${yaml4}
1990
- \`\`\`
1991
-
1992
- ${FEATURES_END2}`;
1993
- const startIdx = existing.indexOf(FEATURES_START2);
1994
- const endIdx = existing.indexOf(FEATURES_END2);
1995
- if (startIdx !== -1 && endIdx !== -1) {
1996
- await this.write("FEATURES.md", existing.slice(0, startIdx) + newSection + existing.slice(endIdx + FEATURES_END2.length));
1997
- } else {
1998
- await this.write("FEATURES.md", `# Features
1999
-
2000
1863
  ${newSection}
2001
1864
  `);
2002
1865
  }
@@ -2013,10 +1876,10 @@ ${newSection}
2013
1876
  if (!content) return [];
2014
1877
  return parseToolMetrics(content);
2015
1878
  }
2016
- /** Aggregate tool call metrics into a cost summary, optionally filtered by sprint. */
2017
- async getCostSummary(sprintNumber) {
1879
+ /** Aggregate tool call metrics into a cost summary, optionally filtered by cycle. */
1880
+ async getCostSummary(cycleNumber) {
2018
1881
  const metrics = await this.readToolMetrics();
2019
- return aggregateCostSummary(metrics, sprintNumber);
1882
+ return aggregateCostSummary(metrics, cycleNumber);
2020
1883
  }
2021
1884
  /** Write a cost snapshot to the Cost Summary section of METRICS.md. */
2022
1885
  async writeCostSnapshot(snapshot) {
@@ -2029,29 +1892,29 @@ ${newSection}
2029
1892
  if (!content) return [];
2030
1893
  return parseCostSnapshots(content);
2031
1894
  }
2032
- // --- Sprint Methodology Metrics ---
2033
- /** Append a sprint metrics snapshot to SPRINT_METRICS.md. */
2034
- async appendSprintMetrics(snapshot) {
1895
+ // --- Cycle Methodology Metrics ---
1896
+ /** Append a cycle metrics snapshot to CYCLE_METRICS.md. */
1897
+ async appendCycleMetrics(snapshot) {
2035
1898
  const content = await this.readOptional("SPRINT_METRICS.md");
2036
1899
  await this.write("SPRINT_METRICS.md", appendSnapshotToContent(snapshot, content));
2037
1900
  }
2038
- /** Read all sprint metrics snapshots from SPRINT_METRICS.md. */
2039
- async readSprintMetrics() {
1901
+ /** Read all cycle metrics snapshots from CYCLE_METRICS.md. */
1902
+ async readCycleMetrics() {
2040
1903
  const content = await this.readOptional("SPRINT_METRICS.md");
2041
1904
  if (!content) return [];
2042
1905
  return parseSnapshots(content);
2043
1906
  }
2044
- // --- Sprints ---
2045
- /** Read all Sprint entities from SPRINTS.md (newest first). */
2046
- async readSprints() {
2047
- const content = await this.readOptional("SPRINTS.md");
1907
+ // --- Cycles ---
1908
+ /** Read all Cycle entities from CYCLES.md (newest first). */
1909
+ async readCycles() {
1910
+ const content = await this.readOptional("CYCLES.md");
2048
1911
  if (!content) return [];
2049
- return parseSprints(content);
1912
+ return parseCycles(content);
2050
1913
  }
2051
- /** Write a new Sprint entity to SPRINTS.md. */
2052
- async createSprint(sprint) {
2053
- const content = await this.readOptional("SPRINTS.md");
2054
- await this.write("SPRINTS.md", prependSprint(sprint, content));
1914
+ /** Write a new Cycle entity to CYCLES.md. */
1915
+ async createCycle(cycle) {
1916
+ const content = await this.readOptional("CYCLES.md");
1917
+ await this.write("CYCLES.md", prependCycle(cycle, content));
2055
1918
  }
2056
1919
  // --- Registries ---
2057
1920
  /** Read module and epic registries from REGISTRIES.md. */
@@ -2081,8 +1944,9 @@ ${newSection}
2081
1944
  ` type: ${full.type}`,
2082
1945
  ` status: ${full.status}`,
2083
1946
  ` content: ${JSON.stringify(full.content)}`,
2084
- ` created_sprint: ${full.createdSprint}`,
2085
- full.actionedSprint != null ? ` actioned_sprint: ${full.actionedSprint}` : null
1947
+ ` created_sprint: ${full.createdCycle}`,
1948
+ full.actionedCycle != null ? ` actioned_cycle: ${full.actionedCycle}` : null,
1949
+ full.target != null ? ` target: ${JSON.stringify(full.target)}` : null
2086
1950
  ].filter(Boolean).join("\n");
2087
1951
  if (!content) {
2088
1952
  await this.write("STRATEGY_RECOMMENDATIONS.md", `${header}${entry}
@@ -2115,7 +1979,7 @@ ${footer}`);
2115
1979
  const statusMatch = block.match(/status:\s+(.+)/);
2116
1980
  const contentMatch = block.match(/content:\s+(.+)/);
2117
1981
  const createdMatch = block.match(/created_sprint:\s+(\d+)/);
2118
- const actionedMatch = block.match(/actioned_sprint:\s+(\d+)/);
1982
+ const actionedMatch = block.match(/actioned_cycle:\s+(\d+)/);
2119
1983
  if (!idMatch || !typeMatch || !statusMatch || !contentMatch || !createdMatch) continue;
2120
1984
  const status = statusMatch[1].trim();
2121
1985
  if (status !== "pending") continue;
@@ -2131,24 +1995,24 @@ ${footer}`);
2131
1995
  type: typeMatch[1].trim(),
2132
1996
  status: "pending",
2133
1997
  content: parsedContent,
2134
- createdSprint: parseInt(createdMatch[1], 10),
2135
- actionedSprint: actionedMatch ? parseInt(actionedMatch[1], 10) : void 0
1998
+ createdCycle: parseInt(createdMatch[1], 10),
1999
+ actionedCycle: actionedMatch ? parseInt(actionedMatch[1], 10) : void 0
2136
2000
  });
2137
2001
  }
2138
2002
  return recs;
2139
2003
  }
2140
2004
  /** Mark a recommendation as actioned. */
2141
- async actionRecommendation(id, sprintNumber) {
2005
+ async actionRecommendation(id, cycleNumber) {
2142
2006
  const content = await this.readOptional("STRATEGY_RECOMMENDATIONS.md");
2143
2007
  if (!content) return;
2144
2008
  const idPattern = new RegExp(`(\\s+-\\s+id:\\s+${id}\\n(?:.*\\n)*?)(\\s+status:\\s+)pending`);
2145
2009
  let updated = content.replace(idPattern, `$1$2actioned`);
2146
2010
  const entryPattern = new RegExp(`(\\s+-\\s+id:\\s+${id}\\n(?:.*\\n)*?)(?=\\s+-\\s+id:|<!-- PAPI-YAML-END -->)`);
2147
2011
  const entryMatch = updated.match(entryPattern);
2148
- if (entryMatch && !entryMatch[0].includes("actioned_sprint:")) {
2149
- const sprintPattern = new RegExp(`(\\s+-\\s+id:\\s+${id}\\n(?:.*\\n)*?\\s+created_sprint:\\s+\\d+)\\n`);
2150
- updated = updated.replace(sprintPattern, `$1
2151
- actioned_sprint: ${sprintNumber}
2012
+ if (entryMatch && !entryMatch[0].includes("actioned_cycle:")) {
2013
+ const cyclePattern = new RegExp(`(\\s+-\\s+id:\\s+${id}\\n(?:.*\\n)*?\\s+created_sprint:\\s+\\d+)\\n`);
2014
+ updated = updated.replace(cyclePattern, `$1
2015
+ actioned_cycle: ${cycleNumber}
2152
2016
  `);
2153
2017
  }
2154
2018
  await this.write("STRATEGY_RECOMMENDATIONS.md", updated);
@@ -2162,7 +2026,7 @@ ${footer}`);
2162
2026
  const full = { ...event, id, createdAt };
2163
2027
  const content = await this.readOptional("DECISION_EVENTS.md");
2164
2028
  const header = "# Decision Events\n\n";
2165
- const entry = `## ${full.decisionId} | ${full.eventType} | Sprint ${full.sprint}
2029
+ const entry = `## ${full.decisionId} | ${full.eventType} | Cycle ${full.cycle}
2166
2030
  - **id:** ${full.id}
2167
2031
  - **source:** ${full.source}
2168
2032
  ` + (full.sourceRef ? `- **sourceRef:** ${full.sourceRef}
@@ -2184,9 +2048,9 @@ ${footer}`);
2184
2048
  const filtered = all.filter((e) => e.decisionId === decisionId);
2185
2049
  return limit ? filtered.slice(0, limit) : filtered;
2186
2050
  }
2187
- async getDecisionEventsSince(sprint) {
2051
+ async getDecisionEventsSince(cycle) {
2188
2052
  const all = await this.parseDecisionEvents();
2189
- return all.filter((e) => e.sprint >= sprint);
2053
+ return all.filter((e) => e.cycle >= cycle);
2190
2054
  }
2191
2055
  async parseDecisionEvents() {
2192
2056
  const content = await this.readOptional("DECISION_EVENTS.md");
@@ -2194,7 +2058,7 @@ ${footer}`);
2194
2058
  const events = [];
2195
2059
  const blocks = content.split("---").filter((b) => b.trim());
2196
2060
  for (const block of blocks) {
2197
- const headingMatch = block.match(/^##\s+(\S+)\s+\|\s+(\S+)\s+\|\s+Sprint\s+(\d+)/m);
2061
+ const headingMatch = block.match(/^##\s+(\S+)\s+\|\s+(\S+)\s+\|\s+Cycle\s+(\d+)/m);
2198
2062
  if (!headingMatch) continue;
2199
2063
  const idMatch = block.match(/\*\*id:\*\*\s+(.+)/);
2200
2064
  const sourceMatch = block.match(/\*\*source:\*\*\s+(.+)/);
@@ -2206,7 +2070,7 @@ ${footer}`);
2206
2070
  id: idMatch[1].trim(),
2207
2071
  decisionId: headingMatch[1],
2208
2072
  eventType: headingMatch[2],
2209
- sprint: parseInt(headingMatch[3], 10),
2073
+ cycle: parseInt(headingMatch[3], 10),
2210
2074
  source: sourceMatch[1].trim(),
2211
2075
  sourceRef: sourceRefMatch?.[1]?.trim(),
2212
2076
  detail: detailMatch?.[1]?.trim(),
@@ -2222,7 +2086,7 @@ ${footer}`);
2222
2086
  const full = { ...score, id, totalScore, createdAt };
2223
2087
  const content = await this.readOptional("DECISION_SCORES.md");
2224
2088
  const header = "# Decision Scores\n\n";
2225
- const entry = `## ${full.decisionId} | Sprint ${full.sprint}
2089
+ const entry = `## ${full.decisionId} | Cycle ${full.cycle}
2226
2090
  - **id:** ${full.id}
2227
2091
  - **effort:** ${full.effort}
2228
2092
  - **risk:** ${full.risk}
@@ -2252,7 +2116,7 @@ ${footer}`);
2252
2116
  const latest = /* @__PURE__ */ new Map();
2253
2117
  for (const s of all) {
2254
2118
  const existing = latest.get(s.decisionId);
2255
- if (!existing || s.sprint > existing.sprint) {
2119
+ if (!existing || s.cycle > existing.cycle) {
2256
2120
  latest.set(s.decisionId, s);
2257
2121
  }
2258
2122
  }
@@ -2264,7 +2128,7 @@ ${footer}`);
2264
2128
  const scores = [];
2265
2129
  const blocks = content.split("---").filter((b) => b.trim());
2266
2130
  for (const block of blocks) {
2267
- const headingMatch = block.match(/^##\s+(\S+)\s+\|\s+Sprint\s+(\d+)/m);
2131
+ const headingMatch = block.match(/^##\s+(\S+)\s+\|\s+Cycle\s+(\d+)/m);
2268
2132
  if (!headingMatch) continue;
2269
2133
  const field = (name) => block.match(new RegExp(`\\*\\*${name}:\\*\\*\\s+(.+)`))?.[1]?.trim();
2270
2134
  const id = field("id");
@@ -2273,7 +2137,7 @@ ${footer}`);
2273
2137
  scores.push({
2274
2138
  id,
2275
2139
  decisionId: headingMatch[1],
2276
- sprint: parseInt(headingMatch[2], 10),
2140
+ cycle: parseInt(headingMatch[2], 10),
2277
2141
  effort: parseInt(field("effort") ?? "0", 10),
2278
2142
  risk: parseInt(field("risk") ?? "0", 10),
2279
2143
  reversibility: parseInt(field("reversibility") ?? "0", 10),
@@ -2289,7 +2153,7 @@ ${footer}`);
2289
2153
  // Entity reference tracking — no-op for md adapter (DB-only feature)
2290
2154
  async logEntityReferences(_refs) {
2291
2155
  }
2292
- async getDecisionUsage(_currentSprint) {
2156
+ async getDecisionUsage(_currentCycle) {
2293
2157
  return [];
2294
2158
  }
2295
2159
  };
@@ -2303,9 +2167,9 @@ function isRealComment(text) {
2303
2167
  const trimmed = text.trim();
2304
2168
  return trimmed.length > 0 && !NONE_PATTERN2.test(trimmed);
2305
2169
  }
2306
- async function detectReviewPatterns(reviews, currentSprint, window = 5, clusterer) {
2170
+ async function detectReviewPatterns(reviews, currentCycle, window = 5, clusterer) {
2307
2171
  const recentReviews = reviews.filter(
2308
- (r) => r.sprint > currentSprint - window && r.sprint <= currentSprint
2172
+ (r) => r.cycle > currentCycle - window && r.cycle <= currentCycle
2309
2173
  );
2310
2174
  let recurringFeedback;
2311
2175
  const realComments = recentReviews.filter((r) => isRealComment(r.comments));
@@ -2367,7 +2231,7 @@ export {
2367
2231
  VALID_TRANSITIONS,
2368
2232
  aggregateCostSummary,
2369
2233
  appendToolMetricToContent,
2370
- calculateSprintMetrics,
2234
+ calculateCycleMetrics,
2371
2235
  detectBuildPatterns,
2372
2236
  detectReviewPatterns,
2373
2237
  hasBuildPatterns,
@@ -2375,24 +2239,21 @@ export {
2375
2239
  isValidStatus,
2376
2240
  isValidTransition,
2377
2241
  parseBuildHandoff,
2242
+ parseCycles,
2378
2243
  parseEffortSize,
2379
- parseFeatures,
2380
2244
  parsePhases,
2381
2245
  parseRegistries,
2382
2246
  parseReviews,
2383
- parseSprints,
2384
2247
  parseToolMetrics,
2248
+ prependCycle,
2385
2249
  prependReview,
2386
- prependSprint,
2387
2250
  serializeBuildHandoff,
2388
- serializeFeatures,
2251
+ serializeCycle,
2252
+ serializeCycles,
2389
2253
  serializePhases,
2390
2254
  serializeRegistries,
2391
2255
  serializeReview,
2392
- serializeSprint,
2393
- serializeSprints,
2394
2256
  serializeToolMetric,
2395
2257
  validateTransition,
2396
- writeFeaturesToContent,
2397
2258
  writePhasesToContent
2398
2259
  };