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.
- package/CHANGELOG.md +9 -0
- package/INSTALL.md +16 -0
- package/README.md +23 -22
- package/dist/{chunk-JDO6O3UO.js → chunk-O365KHHP.js} +139 -3
- package/dist/config.d.ts +13 -1
- package/dist/config.js +178 -132
- package/dist/plan-format.d.ts +121 -3
- package/dist/plan-format.js +176 -35
- package/docs/plan-contract.md +52 -5
- package/herdr-plugin/herdr-plugin.toml +1 -1
- package/install.sh +1 -1
- package/kit/hooks/hooks/approval-gate/index.ts +2 -2
- package/kit/hooks/hooks/merge-guard/lane-plan.ts +1 -1
- package/kit/hooks/hooks/shared/board-lanes.ts +33 -12
- package/kit/hooks/hooks/shared/boards.ts +22 -6
- package/kit/workflow/skills/orchestrate/SKILL.md +2 -1
- package/kit/workflow/skills/orchestrate/references/backend.md +30 -0
- package/kit/workflow/skills/orchestrate/references/close-lane.md +1 -0
- package/kit/workflow/skills/orchestrate/references/herd.md +3 -3
- package/kit/workflow/skills/orchestrate/references/start-lane.md +2 -2
- package/kit/workflow/skills/plan/SKILL.md +3 -1
- package/kit/workflow/skills/plan/references/template-English.md +2 -0
- package/kit/workflow/skills/plan/references/template-German.md +2 -0
- package/package.json +1 -1
- package/schemas/jackherd.schema.json +49 -0
- package/src/backend/command-line.ts +116 -0
- package/src/backend/command.ts +14 -0
- package/src/backend/control.ts +194 -0
- package/src/backend/handover.ts +28 -0
- package/src/backend/herdr.ts +147 -0
- package/src/backend/lock.ts +84 -0
- package/src/backend/process-cwd.ts +32 -0
- package/src/backend/schema.ts +47 -0
- package/src/backend/state.ts +57 -0
- package/src/backend/step.ts +93 -0
- package/src/backend/system.ts +150 -0
- package/src/backend/watch.ts +75 -0
- package/src/brief/command.ts +1 -1
- package/src/brief/lane-facts.ts +2 -2
- package/src/check/command.ts +2 -1
- package/src/check/plan-structure-check.ts +68 -14
- package/src/check/settings.ts +6 -1
- package/src/check/skill-budget-defaults.ts +6 -3
- package/src/cli.ts +1 -0
- package/src/config/schema.ts +6 -0
- package/src/lane/close-board.ts +4 -4
- package/src/lane/close-command.ts +2 -2
- package/src/lane/dev-command.ts +1 -1
- package/src/lane/dev.ts +2 -2
- package/src/lane/port-command.ts +1 -1
- package/src/plan-contract/command.ts +2 -1
- package/src/plan-contract/contract.ts +30 -9
- package/src/plan-format/bare-codes.ts +4 -4
- package/src/plan-format/dependencies.ts +5 -5
- package/src/plan-format/lanes.ts +3 -4
- package/src/plan-format/markdown.ts +125 -10
- package/src/plan-format/open-status.ts +15 -0
- package/src/plan-format/phases.ts +45 -4
- package/src/plan-format/plan-head.ts +31 -9
- package/src/plan-format/plan-structure.ts +43 -7
- package/src/plan-format/progress.ts +3 -0
- package/src/plan-format/sprint-document.ts +4 -4
- package/src/plan-format/unrecognized-sections.ts +21 -0
- package/src/plan-format/waiting.ts +13 -13
- package/src/plans/board-close.ts +25 -16
- package/src/plans/board-pick.ts +1 -1
- package/src/plans/ensure-panes.ts +2 -2
- package/src/plans/layout-summary.ts +8 -1
- package/src/plans/layout.ts +14 -5
- package/src/plans/live.ts +1 -1
- package/src/plans/plan-panes.ts +1 -1
- package/src/plans/settings.ts +6 -0
- package/src/plans/snapshot.ts +9 -5
- package/src/plans/sprint-export.ts +1 -1
- package/src/plans/texts.ts +3 -0
- package/src/plans/view.ts +1 -0
- package/src/resume/coordinator-read.ts +2 -2
- package/src/settled/command.ts +1 -1
- package/src/settled/settle.ts +5 -5
- package/src/shared/shell-word.ts +2 -0
- package/src/sync/render.ts +20 -5
- package/src/sync/sync-plan.ts +6 -2
- package/src/sync/sync.ts +3 -2
- package/src/vocabulary/index.ts +192 -4
- package/src/watch/appointments.ts +13 -20
- package/src/watch/command.ts +32 -5
- package/src/watch/lane-progress.ts +1 -1
- package/src/watch/merge-overlap-read.ts +2 -2
- package/src/watch/plan-missing-read.ts +3 -1
- package/src/watch/reference-paths.ts +4 -0
package/dist/plan-format.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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 };
|
package/dist/plan-format.js
CHANGED
|
@@ -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-
|
|
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
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
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,
|
|
61
|
-
|
|
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,
|
|
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] ?? ""
|
|
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
|
|
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(
|
|
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 = {
|
|
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
|
|
650
|
+
const { sections } = PLAN_VOCABULARY[language];
|
|
550
651
|
const { header, rows } = firstTableWithHeader(subsectionLines(markdown, sections.dependencies));
|
|
551
|
-
const [after, writes, marker] = [
|
|
552
|
-
(
|
|
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
|
|
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
|
|
801
|
-
|
|
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
|
|
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) =>
|
|
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) =>
|
|
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
|
-
|
|
847
|
-
|
|
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
|
|
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) ||
|
|
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
|
|
1101
|
+
const { sections } = PLAN_VOCABULARY[language];
|
|
981
1102
|
const { header, rows } = firstTableWithHeader(subsectionLines(planMarkdown, sections.dependencies));
|
|
982
|
-
const column = header.
|
|
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,
|