jackherd 1.23.3 → 1.25.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/CHANGELOG.md +9 -0
  2. package/INSTALL.md +16 -0
  3. package/README.md +23 -22
  4. package/dist/{chunk-JDO6O3UO.js → chunk-O365KHHP.js} +139 -3
  5. package/dist/config.d.ts +13 -1
  6. package/dist/config.js +178 -132
  7. package/dist/plan-format.d.ts +121 -3
  8. package/dist/plan-format.js +176 -35
  9. package/docs/plan-contract.md +52 -5
  10. package/herdr-plugin/herdr-plugin.toml +1 -1
  11. package/install.sh +1 -1
  12. package/kit/hooks/hooks/approval-gate/index.ts +2 -2
  13. package/kit/hooks/hooks/merge-guard/lane-plan.ts +1 -1
  14. package/kit/hooks/hooks/shared/board-lanes.ts +33 -12
  15. package/kit/hooks/hooks/shared/boards.ts +22 -6
  16. package/kit/workflow/skills/orchestrate/SKILL.md +2 -1
  17. package/kit/workflow/skills/orchestrate/references/backend.md +30 -0
  18. package/kit/workflow/skills/orchestrate/references/close-lane.md +1 -0
  19. package/kit/workflow/skills/orchestrate/references/herd.md +3 -3
  20. package/kit/workflow/skills/orchestrate/references/start-lane.md +2 -2
  21. package/kit/workflow/skills/plan/SKILL.md +3 -1
  22. package/kit/workflow/skills/plan/references/template-English.md +2 -0
  23. package/kit/workflow/skills/plan/references/template-German.md +2 -0
  24. package/package.json +1 -1
  25. package/schemas/jackherd.schema.json +49 -0
  26. package/src/backend/command-line.ts +116 -0
  27. package/src/backend/command.ts +14 -0
  28. package/src/backend/control.ts +194 -0
  29. package/src/backend/handover.ts +28 -0
  30. package/src/backend/herdr.ts +147 -0
  31. package/src/backend/lock.ts +84 -0
  32. package/src/backend/process-cwd.ts +32 -0
  33. package/src/backend/schema.ts +47 -0
  34. package/src/backend/state.ts +57 -0
  35. package/src/backend/step.ts +93 -0
  36. package/src/backend/system.ts +150 -0
  37. package/src/backend/watch.ts +75 -0
  38. package/src/brief/command.ts +1 -1
  39. package/src/brief/lane-facts.ts +2 -2
  40. package/src/check/command.ts +2 -1
  41. package/src/check/plan-structure-check.ts +68 -14
  42. package/src/check/settings.ts +6 -1
  43. package/src/check/skill-budget-defaults.ts +6 -3
  44. package/src/cli.ts +1 -0
  45. package/src/config/schema.ts +6 -0
  46. package/src/lane/close-board.ts +4 -4
  47. package/src/lane/close-command.ts +2 -2
  48. package/src/lane/dev-command.ts +1 -1
  49. package/src/lane/dev.ts +2 -2
  50. package/src/lane/port-command.ts +1 -1
  51. package/src/plan-contract/command.ts +2 -1
  52. package/src/plan-contract/contract.ts +30 -9
  53. package/src/plan-format/bare-codes.ts +4 -4
  54. package/src/plan-format/dependencies.ts +5 -5
  55. package/src/plan-format/lanes.ts +3 -4
  56. package/src/plan-format/markdown.ts +125 -10
  57. package/src/plan-format/open-status.ts +15 -0
  58. package/src/plan-format/phases.ts +45 -4
  59. package/src/plan-format/plan-head.ts +31 -9
  60. package/src/plan-format/plan-structure.ts +43 -7
  61. package/src/plan-format/progress.ts +3 -0
  62. package/src/plan-format/sprint-document.ts +4 -4
  63. package/src/plan-format/unrecognized-sections.ts +21 -0
  64. package/src/plan-format/waiting.ts +13 -13
  65. package/src/plans/board-close.ts +25 -16
  66. package/src/plans/board-pick.ts +1 -1
  67. package/src/plans/ensure-panes.ts +2 -2
  68. package/src/plans/layout-summary.ts +8 -1
  69. package/src/plans/layout.ts +14 -5
  70. package/src/plans/live.ts +1 -1
  71. package/src/plans/plan-panes.ts +1 -1
  72. package/src/plans/settings.ts +6 -0
  73. package/src/plans/snapshot.ts +9 -5
  74. package/src/plans/sprint-export.ts +1 -1
  75. package/src/plans/texts.ts +3 -0
  76. package/src/plans/view.ts +1 -0
  77. package/src/resume/coordinator-read.ts +2 -2
  78. package/src/settled/command.ts +1 -1
  79. package/src/settled/settle.ts +5 -5
  80. package/src/shared/shell-word.ts +2 -0
  81. package/src/sync/render.ts +20 -5
  82. package/src/sync/sync-plan.ts +6 -2
  83. package/src/sync/sync.ts +3 -2
  84. package/src/vocabulary/index.ts +192 -4
  85. package/src/watch/appointments.ts +13 -20
  86. package/src/watch/command.ts +32 -5
  87. package/src/watch/lane-progress.ts +1 -1
  88. package/src/watch/merge-overlap-read.ts +2 -2
  89. package/src/watch/plan-missing-read.ts +3 -1
  90. package/src/watch/reference-paths.ts +4 -0
@@ -3,6 +3,8 @@ declare const PLAN_LANGUAGE: {
3
3
  readonly en: "en";
4
4
  };
5
5
  type PlanLanguage = (typeof PLAN_LANGUAGE)[keyof typeof PLAN_LANGUAGE];
6
+ /** The languages to try when reading: `language` first, then every other known one. */
7
+ declare const languagesFrom: (language: PlanLanguage) => PlanLanguage[];
6
8
  /** The language's name as a reply or a question says it. */
7
9
  declare const LANGUAGE_NAME: Record<PlanLanguage, string>;
8
10
  declare const RESPONSE_ADDRESS: {
@@ -106,6 +108,73 @@ declare const WAITING_VOCABULARY: Record<PlanLanguage, {
106
108
  statusAlso: readonly string[];
107
109
  open: readonly string[];
108
110
  }>;
111
+ /**
112
+ * The closed set of keys a section carries in a marker, `<!-- jh: <key> -->` on the line directly under its heading.
113
+ * The marker names the section whatever language the heading is written in.
114
+ */
115
+ declare const SECTION_KEY: {
116
+ readonly waiting: "waiting";
117
+ readonly appointments: "appointments";
118
+ readonly lanes: "lanes";
119
+ readonly goal: "goal";
120
+ readonly history: "history";
121
+ readonly plans: "plans";
122
+ readonly decisions: "decisions";
123
+ readonly implementation: "implementation";
124
+ readonly model: "model";
125
+ readonly dependencies: "dependencies";
126
+ readonly documentation: "documentation";
127
+ readonly forYou: "for-you";
128
+ readonly next: "next";
129
+ };
130
+ type SectionKey = (typeof SECTION_KEY)[keyof typeof SECTION_KEY];
131
+ declare const isSectionKey: (value: string) => value is SectionKey;
132
+ /**
133
+ * The marker key of each line of the head section (`- **Stand:** <!-- jh: head-status --> …`). The marker names the
134
+ * line whatever language its label is written in.
135
+ */
136
+ declare const HEAD_KEY: Record<HeadLine, string>;
137
+ /**
138
+ * The closed set of keys a line that opens with bold words carries in a marker
139
+ * (`**Kein Review-Gate** <!-- jh: no-review-gate --> reason`). The marker names the line whatever language it is in.
140
+ */
141
+ declare const LINE_KEY: {
142
+ readonly noReviewGate: "no-review-gate";
143
+ readonly purpose: "purpose";
144
+ };
145
+ /**
146
+ * The closed set of keys a table column carries in a marker in its header cell (`Hängt ab von <!-- jh: depends-on -->`).
147
+ * The marker names the column whatever language the header is written in.
148
+ */
149
+ declare const COLUMN_KEY: {
150
+ readonly since: "since";
151
+ readonly item: "item";
152
+ readonly origin: "origin";
153
+ readonly unblocks: "unblocks";
154
+ readonly status: "status";
155
+ readonly when: "when";
156
+ readonly action: "action";
157
+ readonly dependsOn: "depends-on";
158
+ readonly writes: "writes";
159
+ readonly serialMarker: "serial-marker";
160
+ };
161
+ type ColumnKey = (typeof COLUMN_KEY)[keyof typeof COLUMN_KEY];
162
+ /** The header names the column `key` goes by in `language`: its name first, then the names that count as the same column. */
163
+ declare const columnWords: (key: ColumnKey, language: PlanLanguage) => readonly string[];
164
+ /**
165
+ * The closed set of keys a status carries in a marker in its cell (`erledigt <!-- jh: status-done -->`): `open` for a
166
+ * board row that still waits, the others for the status of a plan. The marker names the status whatever language the
167
+ * cell is written in.
168
+ */
169
+ declare const STATUS_KEY: {
170
+ readonly open: "status-open";
171
+ readonly done: "status-done";
172
+ readonly abandoned: "status-abandoned";
173
+ readonly inProgress: "status-in-progress";
174
+ readonly draft: "status-draft";
175
+ };
176
+ type StatusKey = (typeof STATUS_KEY)[keyof typeof STATUS_KEY];
177
+ declare const isStatusKey: (value: string) => value is StatusKey;
109
178
  declare const isPlanLanguage: (value: string) => value is PlanLanguage;
110
179
  declare const PLAN_STATUS: {
111
180
  readonly done: "done";
@@ -173,6 +242,8 @@ type PlanVocabulary = {
173
242
  phase: string;
174
243
  /** Opens the line by which a phase says it has no review gate on purpose: `**Kein Review-Gate:** <reason>`. */
175
244
  noReviewGate: string;
245
+ /** Opens the line directly under a phase heading that says in one sentence what is different afterwards, for whom: `**Worum es geht:** <sentence>`. */
246
+ phasePurpose: string;
176
247
  /** Marks a lane as closed when its name cell contains it. */
177
248
  laneClosed: string;
178
249
  /** Stands in a board row's phases cell when that pull request builds only part of the phase. */
@@ -196,10 +267,35 @@ type PlanVocabulary = {
196
267
  statusWords: Record<PlanStatus, StatusWord>;
197
268
  };
198
269
  declare const PLAN_VOCABULARY: Record<PlanLanguage, PlanVocabulary>;
199
- /** Classifies a free-text `Status:` line; the first matching status in precedence order wins. */
270
+ /** A `<!-- jh: <key> -->` marker anywhere in a text; the key is group 1. */
271
+ declare const MARKER_IN_TEXT: RegExp;
272
+ /**
273
+ * Classifies a free-text `Status:` line. A status marker (`<!-- jh: status-done -->`) decides, any language; else the
274
+ * first matching status in precedence order of the words of `language`, then of any other known language.
275
+ */
200
276
  declare const classifyStatus: (language: PlanLanguage, statusText: string) => PlanStatus;
201
277
  /** `### Phase 3: Title` as the plan spells it in `language`. */
202
278
  declare const phaseHeading: (language: PlanLanguage, id: string, title: string) => string;
279
+ /** The heading text of the section `key` names, as `language` writes it. */
280
+ declare const headingOfSection: (key: SectionKey, language: PlanLanguage) => string;
281
+ /** The key and language of the section a heading text names, read the way a caller spells it; `null` when no language does. */
282
+ declare const sectionOfHeading: (headingText: string) => {
283
+ key: SectionKey;
284
+ language: PlanLanguage;
285
+ } | null;
286
+ /**
287
+ * The closed set of keys a lanes-table cell carries in a marker: `lane-closed` in the name cell of a lane that was
288
+ * cleaned up, `partial-phase` in the phases cell of a row that builds only part of a phase.
289
+ */
290
+ declare const CELL_KEY: {
291
+ readonly laneClosed: "lane-closed";
292
+ readonly partialPhase: "partial-phase";
293
+ };
294
+ type CellKey = (typeof CELL_KEY)[keyof typeof CELL_KEY];
295
+ /** The words of every known language that mark a lane as cleaned up. */
296
+ declare const LANE_CLOSED_WORDS: readonly string[];
297
+ /** The words of every known language that mark a row as building only part of a phase. */
298
+ declare const PARTIAL_PHASE_WORDS: readonly string[];
203
299
  declare const sectionHeading: (language: PlanLanguage, section: keyof PlanVocabulary["sections"]) => string;
204
300
 
205
301
  /**
@@ -260,11 +356,13 @@ type PhaseWorkFields = {
260
356
  /**
261
357
  * `work` is set by the phase source and says nothing for a phase the worktree left as the base has it; a merged phase
262
358
  * is touched as well. `deferred` holds the checkboxes moved to another phase; they are not among `items`, so they
263
- * count neither as open nor as done.
359
+ * count neither as open nor as done. `purpose` is the sentence of the purpose line directly under a `### Phase`
360
+ * heading (`phasePurposeOf`); absent when the phase has none.
264
361
  */
265
362
  type PlanPhase = {
266
363
  id: string;
267
364
  title: string;
365
+ purpose?: string;
268
366
  items: ChecklistItem[];
269
367
  deferred?: DeferredItem[];
270
368
  } & Partial<PhaseWorkFields>;
@@ -283,6 +381,15 @@ declare const endsPhase: (line: string) => boolean;
283
381
  declare const isCheckboxLine: (line: string) => boolean;
284
382
  declare const isOpenCheckboxLine: (line: string) => boolean;
285
383
  declare const phaseHeadingPattern: (language: PlanLanguage) => RegExp;
384
+ /** The bold opening of a line (`**Label:**` or `**Label**:`): the label and the text after it. */
385
+ declare const BOLD_OPENING: RegExp;
386
+ /**
387
+ * The sentence of a purpose line, `**Worum es geht:** <sentence>`, the colon inside or outside the bold; null for any
388
+ * other line and for a purpose line without text after the colon.
389
+ */
390
+ declare const phasePurposeOf: (line: string, language: PlanLanguage) => string | null;
391
+ /** The first non-empty line under a heading decides whether a phase has its purpose line; once `decided`, later lines never do. */
392
+ declare const decidesPurpose: (decided: boolean, line: string) => boolean;
286
393
  /**
287
394
  * The phase an open checkbox line is moved to with `→ P<n>` at its end; null for any other line, a ticked box,
288
395
  * a ✅/⬜ table row, and an arrow to the box's own phase `phaseId`. The one rule the parser and the tally share.
@@ -556,6 +663,8 @@ type PhaseWithOpenBoxes = PhaseWithoutGate & {
556
663
  };
557
664
  /** A `PhaseWithOpenBoxes` whose tasks are all ticked on the base branch. */
558
665
  type MergedPhaseWithOpenBoxes = PhaseWithOpenBoxes;
666
+ /** A `### Phase` heading (1-based line) whose section has tasks and no purpose line directly under the heading. */
667
+ type PhaseWithoutPurpose = PhaseHeading;
559
668
  /** The ids of the `### Phase` headings of a plan. */
560
669
  declare const phaseIdsOfPlan: (markdown: string, language: PlanLanguage) => Set<string>;
561
670
  /**
@@ -565,6 +674,13 @@ declare const phaseIdsOfPlan: (markdown: string, language: PlanLanguage) => Set<
565
674
  * plan does not turn red when it is touched.
566
675
  */
567
676
  declare const newPhasesWithoutReviewGate: (markdown: string, language: PlanLanguage, previousIds: ReadonlySet<string>) => PhaseWithoutGate[];
677
+ /**
678
+ * The phases a plan gained against `previousIds` that have checkboxes and no purpose line as the first non-empty line
679
+ * under the heading (`phasePurposeOf`). Phases the plan already had are not read, like in `newPhasesWithoutReviewGate`.
680
+ */
681
+ declare const newPhasesWithoutPurpose: (markdown: string, language: PlanLanguage, previousIds: ReadonlySet<string>) => PhaseWithoutPurpose[];
682
+ /** Every phase of the plan with tasks and without a purpose line: for a plan read whole, where no phase is older than the change. */
683
+ declare const phasesWithoutPurpose: (markdown: string, language: PlanLanguage) => PhaseWithoutPurpose[];
568
684
  /** Whether the document has a lanes table (`## Spuren`): a board, not a plan. */
569
685
  declare const hasLanesTable: (markdown: string, language: PlanLanguage) => boolean;
570
686
  /**
@@ -593,6 +709,8 @@ declare const boardRowsWithoutPhase: (markdown: string, language: PlanLanguage,
593
709
  type PhaseProgress = {
594
710
  id: string;
595
711
  title: string | null;
712
+ /** The phase's purpose sentence; absent when the plan has none or lacks the phase. */
713
+ purpose?: string;
596
714
  done: number;
597
715
  total: number;
598
716
  items: ChecklistItem[];
@@ -709,4 +827,4 @@ declare const readSprintDocument: (markdown: string, language: PlanLanguage) =>
709
827
  */
710
828
  declare const readPhaseDependencies: (planMarkdown: string, language: PlanLanguage) => Record<string, string>;
711
829
 
712
- export { ADDRESS_FORM, APPOINTMENT_VOCABULARY, APPROVAL_TEXTS, CARD_FIELDS, type ChecklistItem, type ColumnModel, type DeferredItem, type DriftFinding, HEAD_LINE, HEAD_STATUS, type HeadLine, type HeadPhase, type HeadStatus, type HeadVerdict, LANE_PROMPT_OPENING, LANGUAGE_NAME, type LaneProgress, type LongPhaseTitle, type MergedPhaseWithOpenBoxes, type MisplacedDeferral, type ModelRow, NEXT_VOCABULARY, NO_DEPENDENCY_TABLE, PHASE_CELL_LIMIT, PHASE_ID_PREFIX, PHASE_TITLE_LIMIT, PHASE_WORK, PLAN_LANGUAGE, PLAN_STATUS, PLAN_VOCABULARY, PROSE_REASON, type PhaseDependency, type PhaseHeading, type PhaseProgress, type PhaseSchedule, type PhaseTally, type PhaseWithOpenBoxes, type PhaseWithoutGate, type PhaseWithoutRow, type PhaseWork, type PhaseWorkFields, type PlanHead, type PlanHeader, type PlanLanguage, type PlanPhase, type PlanStatus, type PlanStep, type PlanVocabulary, type PlansByHref, type ProseReason, RECOMMENDED_MARK, REPORT_FIELDS, RESPONSE_ADDRESS, type ResponseAddress, type RowWithProse, type RowWithoutPhase, SELF_MERGE_LABEL, SHORT_NAME_LIMIT, SHORT_NAME_PROBLEM, STANDING_APPROVAL_TEXTS, type ShortNameFinding, type ShortNameProblem, type SprintDocument, type SprintLane, type SprintPlanRow, type SprintPlanTable, type StatusWord, type StepProgress, WAITING_VOCABULARY, activeItem, boardRowsWithProse, boardRowsWithoutPhase, checkPlanHead, classifyStatus, columnModels, countDecisions, criticalPath, currentPhase, currentPhaseOf, deferralTarget, differsFromColumn, endsPhase, findDrift, hasLanesTable, isCheckboxLine, isDocumentationGateItem, isOpenCheckboxLine, isPhaseFinished, isPhaseOpen, isPlanLanguage, isReviewGateLine, isTickedUnmerged, laneNameOf, laneProgress, longPhaseTitles, mergedPhasesWithOpenBoxes, misplacedDeferrals, newPhasesWithoutReviewGate, onlyDocumentationOpen, openBoxesOfPhase, parallelGroups, parallelGroupsOfPlan, parseDependencyTable, parseModelTable, parsePlanHead, parsePlanPhases, parseSprintLanes, phaseCount, phaseHeading, phaseHeadingPattern, phaseHeadings, phaseIdOf, phaseIdsOfCell, phaseIdsOfPlan, phaseLabel, phaseLabels, phaseProgress, phaseWaves, phaseWorkFields, phasesText, phasesWithoutDependencyRow, readPhaseDependencies, readPlanHeader, readSprintDocument, resolvePlanPath, sectionHeading, shortNameFindings, shortTitle, sprintTitleOf, sumPhases, tallyPhases, wavesOfPlan, withoutRepeats };
830
+ export { ADDRESS_FORM, APPOINTMENT_VOCABULARY, APPROVAL_TEXTS, BOLD_OPENING, CARD_FIELDS, CELL_KEY, COLUMN_KEY, type CellKey, type ChecklistItem, type ColumnKey, type ColumnModel, type DeferredItem, type DriftFinding, HEAD_KEY, HEAD_LINE, HEAD_STATUS, type HeadLine, type HeadPhase, type HeadStatus, type HeadVerdict, LANE_CLOSED_WORDS, LANE_PROMPT_OPENING, LANGUAGE_NAME, LINE_KEY, type LaneProgress, type LongPhaseTitle, MARKER_IN_TEXT, type MergedPhaseWithOpenBoxes, type MisplacedDeferral, type ModelRow, NEXT_VOCABULARY, NO_DEPENDENCY_TABLE, PARTIAL_PHASE_WORDS, PHASE_CELL_LIMIT, PHASE_ID_PREFIX, PHASE_TITLE_LIMIT, PHASE_WORK, PLAN_LANGUAGE, PLAN_STATUS, PLAN_VOCABULARY, PROSE_REASON, type PhaseDependency, type PhaseHeading, type PhaseProgress, type PhaseSchedule, type PhaseTally, type PhaseWithOpenBoxes, type PhaseWithoutGate, type PhaseWithoutPurpose, type PhaseWithoutRow, type PhaseWork, type PhaseWorkFields, type PlanHead, type PlanHeader, type PlanLanguage, type PlanPhase, type PlanStatus, type PlanStep, type PlanVocabulary, type PlansByHref, type ProseReason, RECOMMENDED_MARK, REPORT_FIELDS, RESPONSE_ADDRESS, type ResponseAddress, type RowWithProse, type RowWithoutPhase, SECTION_KEY, SELF_MERGE_LABEL, SHORT_NAME_LIMIT, SHORT_NAME_PROBLEM, STANDING_APPROVAL_TEXTS, STATUS_KEY, type SectionKey, type ShortNameFinding, type ShortNameProblem, type SprintDocument, type SprintLane, type SprintPlanRow, type SprintPlanTable, type StatusKey, type StatusWord, type StepProgress, WAITING_VOCABULARY, activeItem, boardRowsWithProse, boardRowsWithoutPhase, checkPlanHead, classifyStatus, columnModels, columnWords, countDecisions, criticalPath, currentPhase, currentPhaseOf, decidesPurpose, deferralTarget, differsFromColumn, endsPhase, findDrift, hasLanesTable, headingOfSection, isCheckboxLine, isDocumentationGateItem, isOpenCheckboxLine, isPhaseFinished, isPhaseOpen, isPlanLanguage, isReviewGateLine, isSectionKey, isStatusKey, isTickedUnmerged, laneNameOf, laneProgress, languagesFrom, longPhaseTitles, mergedPhasesWithOpenBoxes, misplacedDeferrals, newPhasesWithoutPurpose, newPhasesWithoutReviewGate, onlyDocumentationOpen, openBoxesOfPhase, parallelGroups, parallelGroupsOfPlan, parseDependencyTable, parseModelTable, parsePlanHead, parsePlanPhases, parseSprintLanes, phaseCount, phaseHeading, phaseHeadingPattern, phaseHeadings, phaseIdOf, phaseIdsOfCell, phaseIdsOfPlan, phaseLabel, phaseLabels, phaseProgress, phasePurposeOf, phaseWaves, phaseWorkFields, phasesText, phasesWithoutDependencyRow, phasesWithoutPurpose, readPhaseDependencies, readPlanHeader, readSprintDocument, resolvePlanPath, sectionHeading, sectionOfHeading, shortNameFindings, shortTitle, sprintTitleOf, sumPhases, tallyPhases, wavesOfPlan, withoutRepeats };
@@ -3,25 +3,40 @@ import {
3
3
  APPOINTMENT_VOCABULARY,
4
4
  APPROVAL_TEXTS,
5
5
  CARD_FIELDS,
6
+ CELL_KEY,
7
+ COLUMN_KEY,
8
+ HEAD_KEY,
6
9
  HEAD_LINE,
10
+ LANE_CLOSED_WORDS,
7
11
  LANE_PROMPT_OPENING,
8
12
  LANGUAGE_NAME,
13
+ LINE_KEY,
14
+ MARKER_IN_TEXT,
9
15
  NEXT_VOCABULARY,
16
+ PARTIAL_PHASE_WORDS,
10
17
  PLAN_LANGUAGE,
11
18
  PLAN_STATUS,
12
19
  PLAN_VOCABULARY,
13
20
  RECOMMENDED_MARK,
14
21
  REPORT_FIELDS,
15
22
  RESPONSE_ADDRESS,
23
+ SECTION_KEY,
16
24
  SELF_MERGE_LABEL,
17
25
  STANDING_APPROVAL_TEXTS,
26
+ STATUS_KEY,
18
27
  WAITING_VOCABULARY,
19
28
  classifyStatus,
29
+ columnWords,
30
+ headingOfSection,
20
31
  isPlanLanguage,
32
+ isSectionKey,
33
+ isStatusKey,
34
+ languagesFrom,
21
35
  phaseHeading,
22
36
  sectionHeading,
37
+ sectionOfHeading,
23
38
  terminalSafe
24
- } from "./chunk-JDO6O3UO.js";
39
+ } from "./chunk-O365KHHP.js";
25
40
 
26
41
  // src/plan-format/lanes.ts
27
42
  import { posix } from "path";
@@ -30,18 +45,74 @@ import { posix } from "path";
30
45
  var SECTION_END = /^##\s/;
31
46
  var TABLE_SEPARATOR = /^\|[\s|:-]+\|$/;
32
47
  var escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
33
- var sectionStart = (markdown, headingText) => {
34
- const heading = new RegExp(`^##\\s+${escapeRegExp(headingText)}\\s*$`);
35
- const start = markdown.split("\n").findIndex((line) => heading.test(line));
36
- return start === -1 ? -1 : start + 1;
48
+ var MARKER = /^<!--\s*jh:\s*([a-z-]+)\s*-->\s*$/;
49
+ var markedKeyOf = (text) => MARKER_IN_TEXT.exec(text)?.[1] ?? null;
50
+ var withoutMarker = (text) => text.replace(MARKER_IN_TEXT, "").trim();
51
+ var lookedForSection = (key, level, language) => {
52
+ const headings = languagesFrom(language).map((candidate) => `"${level} ${headingOfSection(key, candidate)}"`);
53
+ return `(looked for ${headings.join(", ")} or <!-- jh: ${key} -->)`;
54
+ };
55
+ var namesState = (text, key, words) => markedKeyOf(text) === key || words.some((word) => text.includes(word));
56
+ var SECTION_HEADING = /^##\s+(.*?)\s*$/;
57
+ var markerKey = (line) => {
58
+ const key = line === void 0 ? void 0 : MARKER.exec(line)?.[1];
59
+ return key !== void 0 && isSectionKey(key) ? key : null;
60
+ };
61
+ var SECTION_MATCH = { pattern: SECTION_HEADING, matches: (text, heading) => text === heading };
62
+ var SUBSECTION_MATCH = {
63
+ pattern: /^(#{2,4})\s+(.*?)\s*$/,
64
+ matches: (text, heading) => text.startsWith(heading)
65
+ };
66
+ var headingText = (line, { pattern }) => pattern.exec(line)?.at(-1);
67
+ var headingIndexOf = (lines, key, language, match) => {
68
+ const marked = lines.findIndex((line, index) => match.pattern.test(line) && markerKey(lines[index + 1]) === key);
69
+ if (marked !== -1) return marked;
70
+ for (const candidate of languagesFrom(language)) {
71
+ const heading = headingOfSection(key, candidate);
72
+ const found = lines.findIndex((line) => {
73
+ const text = headingText(line, match);
74
+ return text !== void 0 && match.matches(text, heading);
75
+ });
76
+ if (found !== -1) return found;
77
+ }
78
+ return -1;
37
79
  };
38
- var sectionLines = (markdown, headingText) => {
39
- const start = sectionStart(markdown, headingText);
40
- if (start === -1) return [];
41
- const rest = markdown.split("\n").slice(start);
80
+ var findSectionStart = (markdown, key, language) => {
81
+ const lines = markdown.split("\n");
82
+ const heading = headingIndexOf(lines, key, language, SECTION_MATCH);
83
+ if (heading === -1) return -1;
84
+ return heading + (markerKey(lines[heading + 1]) === key ? 2 : 1);
85
+ };
86
+ var findSectionHeading = (markdown, key, language) => headingIndexOf(markdown.split("\n"), key, language, SECTION_MATCH);
87
+ var untilNextSection = (rest) => {
42
88
  const end = rest.findIndex((line) => SECTION_END.test(line));
43
89
  return end === -1 ? rest : rest.slice(0, end);
44
90
  };
91
+ var columnIndex = (header, key, language) => {
92
+ const marked = header.findIndex((cell) => markedKeyOf(cell) === key);
93
+ if (marked !== -1) return marked;
94
+ const names = header.map(withoutMarker);
95
+ for (const candidate of languagesFrom(language)) {
96
+ for (const word of columnWords(key, candidate)) {
97
+ const found = names.indexOf(word);
98
+ if (found !== -1) return found;
99
+ }
100
+ }
101
+ return -1;
102
+ };
103
+ var exactSectionStart = (markdown, headingTextValue) => {
104
+ const heading = new RegExp(`^##\\s+${escapeRegExp(headingTextValue)}\\s*$`);
105
+ const start = markdown.split("\n").findIndex((line) => heading.test(line));
106
+ return start === -1 ? -1 : start + 1;
107
+ };
108
+ var sectionStart = (markdown, headingText2) => {
109
+ const section = sectionOfHeading(headingText2);
110
+ return section === null ? exactSectionStart(markdown, headingText2) : findSectionStart(markdown, section.key, section.language);
111
+ };
112
+ var sectionLines = (markdown, headingText2) => {
113
+ const start = sectionStart(markdown, headingText2);
114
+ return start === -1 ? [] : untilNextSection(markdown.split("\n").slice(start));
115
+ };
45
116
  var tableCells = (line) => line.slice(1, line.endsWith("|") ? -1 : void 0).split("|").map((cell) => terminalSafe(cell).trim());
46
117
  var firstTableRows = (lines) => {
47
118
  const start = lines.findIndex((line) => line.startsWith("|"));
@@ -57,10 +128,14 @@ var headerField = (markdown, name) => {
57
128
  };
58
129
  var firstHeading = (markdown, fallback) => terminalSafe(markdown.match(/^#\s+(.+)$/m)?.[1] ?? fallback).trim();
59
130
  var HEADING = /^(#{2,4})\s+(.*?)\s*$/;
60
- var subsectionStart = (markdown, headingText) => markdown.split("\n").findIndex((line) => HEADING.exec(line)?.[2].startsWith(headingText));
61
- var subsectionLines = (markdown, headingText) => {
131
+ var subsectionStart = (markdown, headingText2) => {
132
+ const section = sectionOfHeading(headingText2);
133
+ if (section !== null) return headingIndexOf(markdown.split("\n"), section.key, section.language, SUBSECTION_MATCH);
134
+ return markdown.split("\n").findIndex((line) => HEADING.exec(line)?.[2].startsWith(headingText2));
135
+ };
136
+ var subsectionLines = (markdown, headingText2) => {
62
137
  const lines = markdown.split("\n");
63
- const start = subsectionStart(markdown, headingText);
138
+ const start = subsectionStart(markdown, headingText2);
64
139
  if (start === -1) return [];
65
140
  const level = HEADING.exec(lines[start])?.[1].length ?? 2;
66
141
  const rest = lines.slice(start + 1);
@@ -166,14 +241,13 @@ var withoutRepeats = (lane, step) => {
166
241
  var parseSprintLanes = (markdown, language) => {
167
242
  const lanes = /* @__PURE__ */ new Map();
168
243
  const phaseId = phaseIdPatterns(language);
169
- const { laneClosed } = PLAN_VOCABULARY[language];
170
244
  let previous = null;
171
245
  for (const cells of firstTable(sectionLines(markdown, PLAN_VOCABULARY[language].sections.lanes))) {
172
246
  const name = laneNameOf(cells[0] ?? "", previous);
173
247
  if (!name) continue;
174
248
  const port = Number.parseInt(cells[1] ?? "", 10);
175
249
  const lane = lanes.get(name) ?? { name, port: Number.isNaN(port) ? null : port, steps: [], closed: false };
176
- const marked = (cells[0] ?? "").includes(laneClosed);
250
+ const marked = namesState(cells[0] ?? "", CELL_KEY.laneClosed, LANE_CLOSED_WORDS);
177
251
  if (lane.closed && !marked && !(cells[0] ?? "").startsWith(CONTINUATION)) {
178
252
  lane.closed = false;
179
253
  lane.steps = [];
@@ -227,7 +301,7 @@ var phaseWorkFields = (work) => work === PHASE_WORK.merged ? { work, touched: tr
227
301
  var REVIEW_GATE = /^\s*\*\*review[- ]gate\b/i;
228
302
  var PHASE_END = /^#{1,3}\s/;
229
303
  var CHECKBOX = /^\s*[-*]\s+\[([ xX])\]\s+(.*)$/;
230
- var SECTION_HEADING = /^##\s+(.+?)\s*$/;
304
+ var SECTION_HEADING2 = /^##\s+(.+?)\s*$/;
231
305
  var TOP_HEADING = /^#\s/;
232
306
  var TABLE_STATUS = { done: "\u2705", open: "\u2B1C" };
233
307
  var ROW_NUMBER = /^\d+(\.\d+)*[a-z]?$/;
@@ -240,6 +314,19 @@ var endsPhase = (line) => PHASE_END.test(line);
240
314
  var isCheckboxLine = (line) => CHECKBOX.test(line);
241
315
  var isOpenCheckboxLine = (line) => line.match(CHECKBOX)?.[1] === " ";
242
316
  var phaseHeadingPattern = (language) => new RegExp(`^###\\s+${escapeRegExp(PLAN_VOCABULARY[language].phase)}\\s+(\\d+[a-z]?)\\b\\s*[:\u2014\u2013-]?\\s*(.*)$`, "i");
317
+ var BOLD_OPENING = /^\s*\*\*([^*]+?)\s*:?\*\*\s*:?\s*(.*)$/;
318
+ var namesPurpose = (line, label, language) => {
319
+ if (markedKeyOf(line) === LINE_KEY.purpose) return true;
320
+ const written = label.trim().toLowerCase();
321
+ return languagesFrom(language).some((candidate) => PLAN_VOCABULARY[candidate].phasePurpose.toLowerCase() === written);
322
+ };
323
+ var phasePurposeOf = (line, language) => {
324
+ const opening = BOLD_OPENING.exec(line);
325
+ if (!opening || !namesPurpose(line, opening[1], language)) return null;
326
+ const text = terminalSafe(withoutMarker(opening[2])).trim();
327
+ return text === "" ? null : text;
328
+ };
329
+ var decidesPurpose = (decided, line) => !decided && line.trim() !== "";
243
330
  var tableCells2 = (line) => {
244
331
  const trimmed = line.trim();
245
332
  if (!trimmed.startsWith("|")) return null;
@@ -266,7 +353,7 @@ var sectionItems = (markdown) => {
266
353
  const sections = /* @__PURE__ */ new Map();
267
354
  let open = null;
268
355
  for (const line of markdown.split("\n")) {
269
- const heading = line.match(SECTION_HEADING);
356
+ const heading = line.match(SECTION_HEADING2);
270
357
  if (heading?.[1]) {
271
358
  open = [];
272
359
  sections.set(heading[1], open);
@@ -341,6 +428,12 @@ var notePointer = (pointers, phase, line) => {
341
428
  const pointer = phase && !pointers.has(phase) ? line.match(SECTION_POINTER)?.[1] : void 0;
342
429
  if (phase && pointer) pointers.set(phase, pointer);
343
430
  };
431
+ var notePurpose = (read, phase, line, language) => {
432
+ if (!phase || !decidesPurpose(read.has(phase), line)) return;
433
+ read.add(phase);
434
+ const purpose = phasePurposeOf(line, language);
435
+ if (purpose !== null) phase.purpose = purpose;
436
+ };
344
437
  var headingOf = (line, phaseHeading2) => line.match(phaseHeading2) ?? line.match(NUMBERED_SECTION);
345
438
  var phaseHeadings = (markdown, language) => {
346
439
  const phaseHeading2 = phaseHeadingPattern(language);
@@ -352,15 +445,22 @@ var phaseHeadings = (markdown, language) => {
352
445
  var parsePlanPhases = (markdown, language) => {
353
446
  const phaseHeading2 = phaseHeadingPattern(language);
354
447
  const pointers = /* @__PURE__ */ new Map();
448
+ const purposeRead = /* @__PURE__ */ new Set();
355
449
  const { phases } = markdown.split("\n").reduce(
356
450
  ({ phases: phases2, open, gate }, line) => {
357
451
  const heading = headingOf(line, phaseHeading2);
358
452
  if (heading) {
359
- const phase = { id: (heading[1] ?? "").toLowerCase(), title: terminalSafe(heading[2] ?? "").trim(), items: [] };
453
+ const phase = {
454
+ id: (heading[1] ?? "").toLowerCase(),
455
+ title: terminalSafe(heading[2] ?? "").trim(),
456
+ items: []
457
+ };
458
+ if (!phaseHeading2.test(line)) purposeRead.add(phase);
360
459
  return { phases: [...phases2, phase], open: phase, gate: false };
361
460
  }
362
461
  if (PHASE_END.test(line)) return { phases: phases2, open: null, gate: false };
363
462
  const inGate = gate || REVIEW_GATE.test(line);
463
+ notePurpose(purposeRead, open, line, language);
364
464
  if (open) fileLine(open, line, inGate);
365
465
  notePointer(pointers, open, line);
366
466
  return { phases: phases2, open, gate: inGate };
@@ -385,6 +485,7 @@ var phaseProgress = (phases, id) => {
385
485
  done: items.filter((item) => item.done).length,
386
486
  total: items.length,
387
487
  items,
488
+ ...phase?.purpose ? { purpose: phase.purpose } : {},
388
489
  ...phase?.deferred ? { deferred: phase.deferred } : {},
389
490
  ...phase?.work ? phaseWorkFields(phase.work) : {}
390
491
  };
@@ -546,10 +647,10 @@ var releasedByFiles = (writes) => writes.flatMap((path) => {
546
647
  return [packageKey(directory.at(-1) ?? ".")];
547
648
  });
548
649
  var parseDependencyTable = (markdown, language) => {
549
- const { sections, columns } = PLAN_VOCABULARY[language];
650
+ const { sections } = PLAN_VOCABULARY[language];
550
651
  const { header, rows } = firstTableWithHeader(subsectionLines(markdown, sections.dependencies));
551
- const [after, writes, marker] = [columns.dependsOn, columns.writes, columns.serialMarker].map(
552
- (name) => header.indexOf(name)
652
+ const [after, writes, marker] = [COLUMN_KEY.dependsOn, COLUMN_KEY.writes, COLUMN_KEY.serialMarker].map(
653
+ (key) => columnIndex(header, key, language)
553
654
  );
554
655
  if (after === -1 || writes === -1 || marker === -1) return null;
555
656
  return rows.flatMap((cells) => {
@@ -790,16 +891,21 @@ var tallyPhases = (markdown, language) => {
790
891
 
791
892
  // src/plan-format/plan-head.ts
792
893
  var HEAD_STATUS = { present: "present", missing: "missing", stale: "stale" };
793
- var SECTION_HEADING2 = /^##\s/;
894
+ var SECTION_HEADING3 = /^##\s/;
794
895
  var HEAD_ENTRY = /^\s*(?:[-*]\s+)?\*\*([^*]+?)\s*:?\*\*\s*:?\s*(.*)$/;
795
896
  var HEAD_LINES = Object.values(HEAD_LINE);
796
897
  var labelOf = (language, line) => PLAN_VOCABULARY[language].head[line].toLowerCase();
898
+ var labelNamed = (text, written, language) => {
899
+ const marked = markedKeyOf(text);
900
+ const byMarker = HEAD_LINES.find((each) => HEAD_KEY[each] === marked);
901
+ if (byMarker) return byMarker;
902
+ return languagesFrom(language).map((candidate) => HEAD_LINES.find((each) => labelOf(candidate, each) === written)).find((each) => each !== void 0);
903
+ };
797
904
  var entryOf = (text, language) => {
798
905
  const match = text.match(HEAD_ENTRY);
799
906
  if (!match) return null;
800
- const written = match[1].trim().toLowerCase();
801
- const label = HEAD_LINES.find((each) => labelOf(language, each) === written);
802
- return label ? { label, text: match[2].trim() } : null;
907
+ const label = labelNamed(text, match[1].trim().toLowerCase(), language);
908
+ return label ? { label, text: withoutMarker(match[2]) } : null;
803
909
  };
804
910
  var phaseOf = (statusText, language) => {
805
911
  const match = statusText?.match(
@@ -809,11 +915,10 @@ var phaseOf = (statusText, language) => {
809
915
  };
810
916
  var parsePlanHead = (markdown, language) => {
811
917
  const lines = markdown.split("\n");
812
- const heading = new RegExp(`^##\\s+${escapeRegExp(PLAN_VOCABULARY[language].sections.forYou)}\\s*$`);
813
- const start = lines.findIndex((text) => heading.test(text));
918
+ const start = findSectionHeading(markdown, SECTION_KEY.forYou, language);
814
919
  if (start === -1) return null;
815
920
  const rest = lines.slice(start + 1);
816
- const end = rest.findIndex((text) => SECTION_HEADING2.test(text));
921
+ const end = rest.findIndex((text) => SECTION_HEADING3.test(text));
817
922
  const texts = {};
818
923
  const lineNumbers = {};
819
924
  for (const [offset, text] of (end === -1 ? rest : rest.slice(0, end)).entries()) {
@@ -829,7 +934,7 @@ var parsePlanHead = (markdown, language) => {
829
934
  lineNumbers,
830
935
  missing: HEAD_LINES.filter((each) => texts[each] === void 0),
831
936
  phase: phaseOf(texts[HEAD_LINE.status], language),
832
- misplaced: lines.slice(0, start).some((text) => SECTION_HEADING2.test(text))
937
+ misplaced: lines.slice(0, start).some((text) => SECTION_HEADING3.test(text))
833
938
  };
834
939
  };
835
940
  var verdict = (status, line, message) => ({ status, line, message });
@@ -843,8 +948,14 @@ var phaseMismatch = ({ current, total }, states) => {
843
948
  };
844
949
  var checkPlanHead = (markdown, language) => {
845
950
  const head = parsePlanHead(markdown, language);
846
- const { sections } = PLAN_VOCABULARY[language];
847
- if (head === null) return verdict(HEAD_STATUS.missing, null, `no \`## ${sections.forYou}\` section`);
951
+ if (head === null) {
952
+ const looked = lookedForSection(SECTION_KEY.forYou, "##", language);
953
+ return verdict(
954
+ HEAD_STATUS.missing,
955
+ null,
956
+ `no \`## ${PLAN_VOCABULARY[language].sections.forYou}\` section ${looked}`
957
+ );
958
+ }
848
959
  if (head.misplaced)
849
960
  return verdict(
850
961
  HEAD_STATUS.stale,
@@ -864,15 +975,21 @@ var checkPlanHead = (markdown, language) => {
864
975
  };
865
976
 
866
977
  // src/plan-format/plan-structure.ts
867
- var noGatePattern = (language) => new RegExp(`^\\s*\\*\\*${escapeRegExp(PLAN_VOCABULARY[language].noReviewGate)}\\b`, "i");
978
+ var NO_GATE_WORDS = new RegExp(
979
+ `^\\s*\\*\\*(?:${Object.values(PLAN_VOCABULARY).map(({ noReviewGate }) => escapeRegExp(noReviewGate)).join("|")})\\b`,
980
+ "i"
981
+ );
982
+ var isNoGateLine = (line) => NO_GATE_WORDS.test(line) || BOLD_OPENING.test(line) && markedKeyOf(line) === LINE_KEY.noReviewGate;
868
983
  var tickCount = (phase) => phase.items.filter((item) => item.done).length;
869
984
  var tasksDone = (phase) => {
870
985
  const tasks = phase.items.filter((item) => !item.gate);
871
986
  return tasks.length > 0 && tasks.every((item) => item.done);
872
987
  };
988
+ var settlePurpose = (section, line, language) => {
989
+ if (decidesPurpose(section.purpose !== null, line)) section.purpose = phasePurposeOf(line, language) !== null;
990
+ };
873
991
  var phaseSections = (markdown, language) => {
874
992
  const heading = phaseHeadingPattern(language);
875
- const noGate = noGatePattern(language);
876
993
  const sections = [];
877
994
  let open = null;
878
995
  for (const [index, line] of markdown.split("\n").entries()) {
@@ -884,14 +1001,16 @@ var phaseSections = (markdown, language) => {
884
1001
  title: terminalSafe(match[2]).trim(),
885
1002
  tasks: false,
886
1003
  gate: false,
1004
+ purpose: null,
887
1005
  openLines: []
888
1006
  };
889
1007
  sections.push(open);
890
1008
  } else if (endsPhase(line)) {
891
1009
  open = null;
892
1010
  } else if (open) {
1011
+ settlePurpose(open, line, language);
893
1012
  open.tasks ||= isCheckboxLine(line);
894
- open.gate ||= isReviewGateLine(line) || noGate.test(line);
1013
+ open.gate ||= isReviewGateLine(line) || isNoGateLine(line);
895
1014
  if (isOpenCheckboxLine(line) && deferralTarget(line, open.id) === null) open.openLines.push(index + 1);
896
1015
  }
897
1016
  }
@@ -899,6 +1018,8 @@ var phaseSections = (markdown, language) => {
899
1018
  };
900
1019
  var phaseIdsOfPlan = (markdown, language) => new Set(phaseSections(markdown, language).map(({ id }) => id));
901
1020
  var newPhasesWithoutReviewGate = (markdown, language, previousIds) => phaseSections(markdown, language).filter(({ id, tasks, gate }) => tasks && !gate && !previousIds.has(id)).map(({ line, id, title }) => ({ line, id, title }));
1021
+ var newPhasesWithoutPurpose = (markdown, language, previousIds) => phaseSections(markdown, language).filter(({ id, tasks, purpose }) => tasks && !purpose && !previousIds.has(id)).map(({ line, id, title }) => ({ line, id, title }));
1022
+ var phasesWithoutPurpose = (markdown, language) => newPhasesWithoutPurpose(markdown, language, /* @__PURE__ */ new Set());
902
1023
  var hasLanesTable = (markdown, language) => sectionLines(markdown, PLAN_VOCABULARY[language].sections.lanes).some((line) => line.startsWith("|"));
903
1024
  var mergedPhasesWithOpenBoxes = (markdown, base, language) => {
904
1025
  const baseById = new Map(parsePlanPhases(base, language).map((phase) => [phase.id, phase]));
@@ -977,9 +1098,9 @@ var readSprintDocument = (markdown, language) => {
977
1098
  };
978
1099
  };
979
1100
  var readPhaseDependencies = (planMarkdown, language) => {
980
- const { sections, columns } = PLAN_VOCABULARY[language];
1101
+ const { sections } = PLAN_VOCABULARY[language];
981
1102
  const { header, rows } = firstTableWithHeader(subsectionLines(planMarkdown, sections.dependencies));
982
- const column = header.indexOf(columns.dependsOn);
1103
+ const column = columnIndex(header, COLUMN_KEY.dependsOn, language);
983
1104
  if (column === -1) return {};
984
1105
  const entries = rows.flatMap((cells) => {
985
1106
  const id = PHASE_ID_IN_CELL.exec(cells[0] ?? "")?.[1];
@@ -991,13 +1112,21 @@ export {
991
1112
  ADDRESS_FORM,
992
1113
  APPOINTMENT_VOCABULARY,
993
1114
  APPROVAL_TEXTS,
1115
+ BOLD_OPENING,
994
1116
  CARD_FIELDS,
1117
+ CELL_KEY,
1118
+ COLUMN_KEY,
1119
+ HEAD_KEY,
995
1120
  HEAD_LINE,
996
1121
  HEAD_STATUS,
1122
+ LANE_CLOSED_WORDS,
997
1123
  LANE_PROMPT_OPENING,
998
1124
  LANGUAGE_NAME,
1125
+ LINE_KEY,
1126
+ MARKER_IN_TEXT,
999
1127
  NEXT_VOCABULARY,
1000
1128
  NO_DEPENDENCY_TABLE,
1129
+ PARTIAL_PHASE_WORDS,
1001
1130
  PHASE_CELL_LIMIT,
1002
1131
  PHASE_ID_PREFIX,
1003
1132
  PHASE_TITLE_LIMIT,
@@ -1009,10 +1138,12 @@ export {
1009
1138
  RECOMMENDED_MARK,
1010
1139
  REPORT_FIELDS,
1011
1140
  RESPONSE_ADDRESS,
1141
+ SECTION_KEY,
1012
1142
  SELF_MERGE_LABEL,
1013
1143
  SHORT_NAME_LIMIT,
1014
1144
  SHORT_NAME_PROBLEM,
1015
1145
  STANDING_APPROVAL_TEXTS,
1146
+ STATUS_KEY,
1016
1147
  WAITING_VOCABULARY,
1017
1148
  activeItem,
1018
1149
  boardRowsWithProse,
@@ -1020,15 +1151,18 @@ export {
1020
1151
  checkPlanHead,
1021
1152
  classifyStatus,
1022
1153
  columnModels,
1154
+ columnWords,
1023
1155
  countDecisions,
1024
1156
  criticalPath,
1025
1157
  currentPhase,
1026
1158
  currentPhaseOf,
1159
+ decidesPurpose,
1027
1160
  deferralTarget,
1028
1161
  differsFromColumn,
1029
1162
  endsPhase,
1030
1163
  findDrift,
1031
1164
  hasLanesTable,
1165
+ headingOfSection,
1032
1166
  isCheckboxLine,
1033
1167
  isDocumentationGateItem,
1034
1168
  isOpenCheckboxLine,
@@ -1036,12 +1170,16 @@ export {
1036
1170
  isPhaseOpen,
1037
1171
  isPlanLanguage,
1038
1172
  isReviewGateLine,
1173
+ isSectionKey,
1174
+ isStatusKey,
1039
1175
  isTickedUnmerged,
1040
1176
  laneNameOf,
1041
1177
  laneProgress,
1178
+ languagesFrom,
1042
1179
  longPhaseTitles,
1043
1180
  mergedPhasesWithOpenBoxes,
1044
1181
  misplacedDeferrals,
1182
+ newPhasesWithoutPurpose,
1045
1183
  newPhasesWithoutReviewGate,
1046
1184
  onlyDocumentationOpen,
1047
1185
  openBoxesOfPhase,
@@ -1062,15 +1200,18 @@ export {
1062
1200
  phaseLabel,
1063
1201
  phaseLabels,
1064
1202
  phaseProgress,
1203
+ phasePurposeOf,
1065
1204
  phaseWaves,
1066
1205
  phaseWorkFields,
1067
1206
  phasesText,
1068
1207
  phasesWithoutDependencyRow,
1208
+ phasesWithoutPurpose,
1069
1209
  readPhaseDependencies,
1070
1210
  readPlanHeader,
1071
1211
  readSprintDocument,
1072
1212
  resolvePlanPath,
1073
1213
  sectionHeading,
1214
+ sectionOfHeading,
1074
1215
  shortNameFindings,
1075
1216
  shortTitle,
1076
1217
  sprintTitleOf,