openplanr 1.2.8 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/README.md +30 -3
  2. package/dist/agents/task-parser.d.ts.map +1 -1
  3. package/dist/agents/task-parser.js +8 -34
  4. package/dist/agents/task-parser.js.map +1 -1
  5. package/dist/ai/prompts/prompt-builder.d.ts +14 -0
  6. package/dist/ai/prompts/prompt-builder.d.ts.map +1 -1
  7. package/dist/ai/prompts/prompt-builder.js +32 -1
  8. package/dist/ai/prompts/prompt-builder.js.map +1 -1
  9. package/dist/ai/prompts/system-prompts.d.ts +3 -2
  10. package/dist/ai/prompts/system-prompts.d.ts.map +1 -1
  11. package/dist/ai/prompts/system-prompts.js +117 -7
  12. package/dist/ai/prompts/system-prompts.js.map +1 -1
  13. package/dist/ai/schemas/ai-response-schemas.d.ts +62 -0
  14. package/dist/ai/schemas/ai-response-schemas.d.ts.map +1 -1
  15. package/dist/ai/schemas/ai-response-schemas.js +51 -1
  16. package/dist/ai/schemas/ai-response-schemas.js.map +1 -1
  17. package/dist/ai/types.d.ts.map +1 -1
  18. package/dist/ai/types.js +2 -0
  19. package/dist/ai/types.js.map +1 -1
  20. package/dist/cli/commands/backlog.d.ts +12 -0
  21. package/dist/cli/commands/backlog.d.ts.map +1 -1
  22. package/dist/cli/commands/backlog.js +88 -2
  23. package/dist/cli/commands/backlog.js.map +1 -1
  24. package/dist/cli/commands/config.d.ts.map +1 -1
  25. package/dist/cli/commands/config.js +8 -2
  26. package/dist/cli/commands/config.js.map +1 -1
  27. package/dist/cli/commands/linear.d.ts +8 -0
  28. package/dist/cli/commands/linear.d.ts.map +1 -0
  29. package/dist/cli/commands/linear.js +550 -0
  30. package/dist/cli/commands/linear.js.map +1 -0
  31. package/dist/cli/commands/quick.d.ts +17 -0
  32. package/dist/cli/commands/quick.d.ts.map +1 -1
  33. package/dist/cli/commands/quick.js +31 -15
  34. package/dist/cli/commands/quick.js.map +1 -1
  35. package/dist/cli/commands/revise.d.ts +9 -8
  36. package/dist/cli/commands/revise.d.ts.map +1 -1
  37. package/dist/cli/commands/revise.js +93 -25
  38. package/dist/cli/commands/revise.js.map +1 -1
  39. package/dist/cli/commands/spec.d.ts +28 -0
  40. package/dist/cli/commands/spec.d.ts.map +1 -0
  41. package/dist/cli/commands/spec.js +529 -0
  42. package/dist/cli/commands/spec.js.map +1 -0
  43. package/dist/cli/index.js +4 -0
  44. package/dist/cli/index.js.map +1 -1
  45. package/dist/models/schema.d.ts +44 -0
  46. package/dist/models/schema.d.ts.map +1 -1
  47. package/dist/models/schema.js +50 -0
  48. package/dist/models/schema.js.map +1 -1
  49. package/dist/models/types.d.ts +188 -3
  50. package/dist/models/types.d.ts.map +1 -1
  51. package/dist/services/artifact-gathering.d.ts +4 -0
  52. package/dist/services/artifact-gathering.d.ts.map +1 -1
  53. package/dist/services/artifact-gathering.js +1 -1
  54. package/dist/services/artifact-gathering.js.map +1 -1
  55. package/dist/services/artifact-service.d.ts +12 -1
  56. package/dist/services/artifact-service.d.ts.map +1 -1
  57. package/dist/services/artifact-service.js +49 -6
  58. package/dist/services/artifact-service.js.map +1 -1
  59. package/dist/services/atomic-write-service.d.ts +2 -2
  60. package/dist/services/atomic-write-service.js +2 -2
  61. package/dist/services/audit-log-service.d.ts +3 -6
  62. package/dist/services/audit-log-service.d.ts.map +1 -1
  63. package/dist/services/audit-log-service.js +4 -7
  64. package/dist/services/audit-log-service.js.map +1 -1
  65. package/dist/services/cascade-service.d.ts +2 -2
  66. package/dist/services/cascade-service.js +3 -3
  67. package/dist/services/cascade-service.js.map +1 -1
  68. package/dist/services/config-service.d.ts.map +1 -1
  69. package/dist/services/config-service.js +1 -0
  70. package/dist/services/config-service.js.map +1 -1
  71. package/dist/services/credentials-service.js +2 -2
  72. package/dist/services/credentials-service.js.map +1 -1
  73. package/dist/services/diff-service.d.ts +1 -1
  74. package/dist/services/diff-service.js +1 -1
  75. package/dist/services/evidence-verifier.d.ts +1 -1
  76. package/dist/services/evidence-verifier.d.ts.map +1 -1
  77. package/dist/services/evidence-verifier.js +5 -2
  78. package/dist/services/evidence-verifier.js.map +1 -1
  79. package/dist/services/git-service.d.ts +4 -4
  80. package/dist/services/git-service.js +4 -4
  81. package/dist/services/graph-integrity.d.ts +2 -3
  82. package/dist/services/graph-integrity.d.ts.map +1 -1
  83. package/dist/services/graph-integrity.js +2 -3
  84. package/dist/services/graph-integrity.js.map +1 -1
  85. package/dist/services/linear/body-formatters.d.ts +69 -0
  86. package/dist/services/linear/body-formatters.d.ts.map +1 -0
  87. package/dist/services/linear/body-formatters.js +183 -0
  88. package/dist/services/linear/body-formatters.js.map +1 -0
  89. package/dist/services/linear/constants.d.ts +61 -0
  90. package/dist/services/linear/constants.d.ts.map +1 -0
  91. package/dist/services/linear/constants.js +84 -0
  92. package/dist/services/linear/constants.js.map +1 -0
  93. package/dist/services/linear/errors.d.ts +14 -0
  94. package/dist/services/linear/errors.d.ts.map +1 -0
  95. package/dist/services/linear/errors.js +106 -0
  96. package/dist/services/linear/errors.js.map +1 -0
  97. package/dist/services/linear/estimate-resolver.d.ts +50 -0
  98. package/dist/services/linear/estimate-resolver.d.ts.map +1 -0
  99. package/dist/services/linear/estimate-resolver.js +82 -0
  100. package/dist/services/linear/estimate-resolver.js.map +1 -0
  101. package/dist/services/linear/plan-builders.d.ts +64 -0
  102. package/dist/services/linear/plan-builders.d.ts.map +1 -0
  103. package/dist/services/linear/plan-builders.js +237 -0
  104. package/dist/services/linear/plan-builders.js.map +1 -0
  105. package/dist/services/linear/scope-loaders.d.ts +79 -0
  106. package/dist/services/linear/scope-loaders.d.ts.map +1 -0
  107. package/dist/services/linear/scope-loaders.js +227 -0
  108. package/dist/services/linear/scope-loaders.js.map +1 -0
  109. package/dist/services/linear/strategy-context.d.ts +66 -0
  110. package/dist/services/linear/strategy-context.d.ts.map +1 -0
  111. package/dist/services/linear/strategy-context.js +121 -0
  112. package/dist/services/linear/strategy-context.js.map +1 -0
  113. package/dist/services/linear-mapping-service.d.ts +11 -0
  114. package/dist/services/linear-mapping-service.d.ts.map +1 -0
  115. package/dist/services/linear-mapping-service.js +220 -0
  116. package/dist/services/linear-mapping-service.js.map +1 -0
  117. package/dist/services/linear-pull-service.d.ts +137 -0
  118. package/dist/services/linear-pull-service.d.ts.map +1 -0
  119. package/dist/services/linear-pull-service.js +720 -0
  120. package/dist/services/linear-pull-service.js.map +1 -0
  121. package/dist/services/linear-push-service.d.ts +86 -0
  122. package/dist/services/linear-push-service.d.ts.map +1 -0
  123. package/dist/services/linear-push-service.js +956 -0
  124. package/dist/services/linear-push-service.js.map +1 -0
  125. package/dist/services/linear-service.d.ts +122 -0
  126. package/dist/services/linear-service.d.ts.map +1 -0
  127. package/dist/services/linear-service.js +361 -0
  128. package/dist/services/linear-service.js.map +1 -0
  129. package/dist/services/prompt-service.d.ts +19 -0
  130. package/dist/services/prompt-service.d.ts.map +1 -1
  131. package/dist/services/prompt-service.js +64 -0
  132. package/dist/services/prompt-service.js.map +1 -1
  133. package/dist/services/revise-apply-service.d.ts +55 -0
  134. package/dist/services/revise-apply-service.d.ts.map +1 -0
  135. package/dist/services/revise-apply-service.js +255 -0
  136. package/dist/services/revise-apply-service.js.map +1 -0
  137. package/dist/services/revise-cache-service.d.ts +1 -1
  138. package/dist/services/revise-cache-service.js +1 -1
  139. package/dist/services/revise-plan-service.d.ts +38 -0
  140. package/dist/services/revise-plan-service.d.ts.map +1 -0
  141. package/dist/services/revise-plan-service.js +151 -0
  142. package/dist/services/revise-plan-service.js.map +1 -0
  143. package/dist/services/revise-service.d.ts +18 -11
  144. package/dist/services/revise-service.d.ts.map +1 -1
  145. package/dist/services/revise-service.js +57 -12
  146. package/dist/services/revise-service.js.map +1 -1
  147. package/dist/services/spec-service.d.ts +292 -0
  148. package/dist/services/spec-service.d.ts.map +1 -0
  149. package/dist/services/spec-service.js +805 -0
  150. package/dist/services/spec-service.js.map +1 -0
  151. package/dist/services/template-sections.d.ts +1 -1
  152. package/dist/services/template-sections.js +1 -1
  153. package/dist/templates/backlog/backlog-item.md.hbs +3 -0
  154. package/dist/templates/quick/quick-task.md.hbs +6 -0
  155. package/dist/templates/spec/spec-shaped.md.hbs +89 -0
  156. package/dist/templates/spec/spec.md.hbs +68 -0
  157. package/dist/templates/spec/story.md.hbs +51 -0
  158. package/dist/templates/spec/task.md.hbs +98 -0
  159. package/dist/utils/constants.d.ts +18 -0
  160. package/dist/utils/constants.d.ts.map +1 -1
  161. package/dist/utils/constants.js +25 -0
  162. package/dist/utils/constants.js.map +1 -1
  163. package/dist/utils/diff.d.ts +22 -1
  164. package/dist/utils/diff.d.ts.map +1 -1
  165. package/dist/utils/diff.js +136 -1
  166. package/dist/utils/diff.js.map +1 -1
  167. package/dist/utils/markdown.d.ts +23 -0
  168. package/dist/utils/markdown.d.ts.map +1 -1
  169. package/dist/utils/markdown.js +79 -0
  170. package/dist/utils/markdown.js.map +1 -1
  171. package/package.json +3 -2
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Pure data loaders that hydrate OpenPlanr artifacts into the shapes the
3
+ * Linear push expects. No Linear API calls, no mutations — just filesystem
4
+ * reads + frontmatter parsing. Every scope-level push function routes
5
+ * through one of these loaders first.
6
+ */
7
+ import { listArtifacts, readArtifact, readArtifactRaw } from '../artifact-service.js';
8
+ import { toOptionalString, toOptionalStringArray } from './body-formatters.js';
9
+ import { toOptionalStrategy } from './strategy-context.js';
10
+ function sortByArtifactId(a, b) {
11
+ return a.id.localeCompare(b.id, undefined, { numeric: true });
12
+ }
13
+ function asTaskStatus(s) {
14
+ if (s === 'pending' || s === 'in-progress' || s === 'done')
15
+ return s;
16
+ return 'pending';
17
+ }
18
+ function nowIso() {
19
+ return new Date().toISOString().split('T')[0];
20
+ }
21
+ /**
22
+ * Load the full epic subtree (epic frontmatter + features + stories per
23
+ * feature + task-file ids per feature). Filters by `epicId` on each child
24
+ * artifact.
25
+ */
26
+ export async function loadLinearPushScope(projectDir, config, epicId) {
27
+ const epicArt = await readArtifact(projectDir, config, 'epic', epicId);
28
+ if (!epicArt)
29
+ return null;
30
+ const d = epicArt.data;
31
+ const epic = {
32
+ id: d.id || epicId,
33
+ title: d.title || '',
34
+ createdAt: d.createdAt || d.created || nowIso(),
35
+ updatedAt: d.updatedAt || d.updated || nowIso(),
36
+ filePath: epicArt.filePath,
37
+ owner: d.owner || '',
38
+ businessValue: d.businessValue || '',
39
+ targetUsers: d.targetUsers || '',
40
+ problemStatement: d.problemStatement || '',
41
+ solutionOverview: d.solutionOverview || '',
42
+ successCriteria: d.successCriteria || '',
43
+ keyFeatures: d.keyFeatures || [],
44
+ dependencies: d.dependencies || '',
45
+ risks: d.risks || '',
46
+ featureIds: d.featureIds || [],
47
+ linearProjectId: toOptionalString(d.linearProjectId),
48
+ linearProjectIdentifier: toOptionalString(d.linearProjectIdentifier),
49
+ linearProjectUrl: toOptionalString(d.linearProjectUrl),
50
+ linearMappingStrategy: toOptionalStrategy(d.linearMappingStrategy),
51
+ linearMilestoneId: toOptionalString(d.linearMilestoneId),
52
+ linearLabelId: toOptionalString(d.linearLabelId),
53
+ };
54
+ const allFeatures = (await listArtifacts(projectDir, config, 'feature')).sort(sortByArtifactId);
55
+ const allStories = (await listArtifacts(projectDir, config, 'story')).sort(sortByArtifactId);
56
+ const allTasks = (await listArtifacts(projectDir, config, 'task')).sort(sortByArtifactId);
57
+ const featuresUnderEpic = [];
58
+ for (const f of allFeatures) {
59
+ const a = await readArtifact(projectDir, config, 'feature', f.id);
60
+ if (!a || a.data.epicId !== epicId)
61
+ continue;
62
+ const fd = a.data;
63
+ const feature = {
64
+ id: fd.id || f.id,
65
+ title: fd.title || f.title,
66
+ createdAt: fd.createdAt || fd.created || nowIso(),
67
+ updatedAt: fd.updatedAt || fd.updated || nowIso(),
68
+ filePath: a.filePath,
69
+ epicId: fd.epicId,
70
+ owner: fd.owner || '',
71
+ status: asTaskStatus(fd.status),
72
+ overview: fd.overview || '',
73
+ functionalRequirements: fd.functionalRequirements || [],
74
+ storyIds: fd.storyIds || [],
75
+ linearIssueId: toOptionalString(fd.linearIssueId),
76
+ linearIssueIdentifier: toOptionalString(fd.linearIssueIdentifier),
77
+ linearIssueUrl: toOptionalString(fd.linearIssueUrl),
78
+ linearProjectMilestoneId: toOptionalString(fd.linearProjectMilestoneId),
79
+ linearLabelIds: toOptionalStringArray(fd.linearLabelIds),
80
+ };
81
+ const stories = [];
82
+ for (const s of allStories) {
83
+ const st = await readArtifact(projectDir, config, 'story', s.id);
84
+ if (!st || st.data.featureId !== feature.id)
85
+ continue;
86
+ const sd = st.data;
87
+ const story = {
88
+ id: sd.id || s.id,
89
+ title: sd.title || s.title,
90
+ createdAt: sd.createdAt || sd.created || nowIso(),
91
+ updatedAt: sd.updatedAt || sd.updated || nowIso(),
92
+ filePath: st.filePath,
93
+ featureId: sd.featureId,
94
+ status: asTaskStatus(sd.status),
95
+ role: sd.role || '',
96
+ goal: sd.goal || '',
97
+ benefit: sd.benefit || '',
98
+ acceptanceCriteria: sd.acceptanceCriteria || '',
99
+ additionalNotes: toOptionalString(sd.additionalNotes),
100
+ linearIssueId: toOptionalString(sd.linearIssueId),
101
+ linearIssueIdentifier: toOptionalString(sd.linearIssueIdentifier),
102
+ linearIssueUrl: toOptionalString(sd.linearIssueUrl),
103
+ linearParentIssueId: toOptionalString(sd.linearParentIssueId),
104
+ linearProjectMilestoneId: toOptionalString(sd.linearProjectMilestoneId),
105
+ linearLabelIds: toOptionalStringArray(sd.linearLabelIds),
106
+ };
107
+ stories.push({
108
+ id: story.id,
109
+ title: story.title,
110
+ data: story,
111
+ frontmatter: sd,
112
+ });
113
+ }
114
+ const taskFiles = [];
115
+ for (const t of allTasks) {
116
+ const ta = await readArtifact(projectDir, config, 'task', t.id);
117
+ const pfeat = toOptionalString(ta?.data.featureId);
118
+ if (pfeat === feature.id) {
119
+ taskFiles.push({ id: t.id, title: t.title });
120
+ }
121
+ }
122
+ featuresUnderEpic.push({
123
+ id: feature.id,
124
+ title: feature.title,
125
+ data: feature,
126
+ frontmatter: fd,
127
+ stories,
128
+ taskFiles,
129
+ });
130
+ }
131
+ return { epic, features: featuresUnderEpic };
132
+ }
133
+ /**
134
+ * Parent-chain context needed to push a feature: the feature itself (with
135
+ * its stories and task files) plus its parent epic. Returns `null` if the
136
+ * feature can't be resolved or has no valid `epicId` pointer.
137
+ */
138
+ export async function loadForFeature(projectDir, config, featureId) {
139
+ const featureArt = await readArtifact(projectDir, config, 'feature', featureId);
140
+ if (!featureArt)
141
+ return null;
142
+ const parentEpicId = toOptionalString(featureArt.data.epicId);
143
+ if (!parentEpicId)
144
+ return null;
145
+ const epicScope = await loadLinearPushScope(projectDir, config, parentEpicId);
146
+ if (!epicScope)
147
+ return null;
148
+ const sf = epicScope.features.find((f) => f.id === featureId);
149
+ if (!sf)
150
+ return null;
151
+ return { epic: epicScope.epic, sf };
152
+ }
153
+ /**
154
+ * Parent-chain context needed to push a story: the story itself, its
155
+ * feature (with sibling stories + tasklists) and the containing epic.
156
+ * Returns `null` if any link in the chain is missing.
157
+ */
158
+ export async function loadForStory(projectDir, config, storyId) {
159
+ const storyArt = await readArtifact(projectDir, config, 'story', storyId);
160
+ if (!storyArt)
161
+ return null;
162
+ const parentFeatureId = toOptionalString(storyArt.data.featureId);
163
+ if (!parentFeatureId)
164
+ return null;
165
+ const ctx = await loadForFeature(projectDir, config, parentFeatureId);
166
+ if (!ctx)
167
+ return null;
168
+ const story = ctx.sf.stories.find((s) => s.id === storyId);
169
+ if (!story)
170
+ return null;
171
+ return { epic: ctx.epic, sf: ctx.sf, story };
172
+ }
173
+ /**
174
+ * Parent-chain context needed to push a task file: the containing feature
175
+ * (with all its task files merged into one Linear sub-issue body) and the
176
+ * epic.
177
+ */
178
+ export async function loadForTaskFile(projectDir, config, taskId) {
179
+ const taskArt = await readArtifact(projectDir, config, 'task', taskId);
180
+ if (!taskArt)
181
+ return null;
182
+ const parentFeatureId = toOptionalString(taskArt.data.featureId);
183
+ if (!parentFeatureId)
184
+ return null;
185
+ return loadForFeature(projectDir, config, parentFeatureId);
186
+ }
187
+ /**
188
+ * Frontmatter sanity check for standalone artifacts. Every pushable file
189
+ * MUST have at least a real `title` — otherwise we end up creating a
190
+ * Linear issue whose title is just the artifact id (e.g. "QT-015"), and
191
+ * then the subsequent `updateArtifactFields` write-back fails because the
192
+ * file's frontmatter block is malformed. Bail here, before any API call,
193
+ * so the Linear side stays clean.
194
+ */
195
+ function requireFrontmatter(kind, id, filePath, data) {
196
+ const title = toOptionalString(data.title);
197
+ if (!title) {
198
+ throw new Error(`${kind} ${id} has no \`title\` field in its frontmatter.\n ${filePath}\n Fix the file's frontmatter (must be a \`---\`-delimited YAML block with at least \`id\` and \`title\`) and re-run. No changes were pushed to Linear.`);
199
+ }
200
+ }
201
+ export async function loadForQuickTask(projectDir, config, qtId) {
202
+ const art = await readArtifact(projectDir, config, 'quick', qtId);
203
+ if (!art)
204
+ return null;
205
+ requireFrontmatter('Quick task', qtId, art.filePath, art.data);
206
+ const raw = (await readArtifactRaw(projectDir, config, 'quick', qtId)) ?? '';
207
+ return {
208
+ id: art.data.id || qtId,
209
+ title: art.data.title,
210
+ raw,
211
+ frontmatter: art.data,
212
+ };
213
+ }
214
+ export async function loadForBacklogItem(projectDir, config, blId) {
215
+ const art = await readArtifact(projectDir, config, 'backlog', blId);
216
+ if (!art)
217
+ return null;
218
+ requireFrontmatter('Backlog item', blId, art.filePath, art.data);
219
+ const raw = (await readArtifactRaw(projectDir, config, 'backlog', blId)) ?? '';
220
+ return {
221
+ id: art.data.id || blId,
222
+ title: art.data.title,
223
+ raw,
224
+ frontmatter: art.data,
225
+ };
226
+ }
227
+ //# sourceMappingURL=scope-loaders.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope-loaders.js","sourceRoot":"","sources":["../../../src/services/linear/scope-loaders.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Epic-mapping strategy resolution + descendant-propagation context.
3
+ *
4
+ * `StrategyContext` is the read-only bundle that a single-scope push
5
+ * (feature / story / tasklist / QT / BL) needs to attach descendant issues
6
+ * into Linear correctly: `projectId` is always set, `milestoneId` / `labelId`
7
+ * are populated per the epic's strategy. The epic-scope push also builds a
8
+ * context on first push (interactive mapping prompt or `--as` flag) — that
9
+ * builder lives inside `pushEpicScope` because it performs Linear mutations.
10
+ */
11
+ import type { LinearClient } from '@linear/sdk';
12
+ import type { Epic, LinearMappingStrategy, OpenPlanrConfig } from '../../models/types.js';
13
+ /**
14
+ * Type → Linear label name for auto-applied GitHub-style filters. Users
15
+ * can override names via `linear.typeLabels` in `.planr/config.json`.
16
+ */
17
+ export type LinearLabeledArtifactType = 'feature' | 'story' | 'task' | 'quick' | 'backlog';
18
+ export declare function resolveTypeLabelName(config: OpenPlanrConfig, type: LinearLabeledArtifactType): string;
19
+ export interface StrategyContext {
20
+ strategy: LinearMappingStrategy;
21
+ /** Always set — the Linear project that contains the epic's descendants. */
22
+ projectId: string;
23
+ /** Set when strategy === 'milestone-of' — written to every descendant issue. */
24
+ milestoneId?: string;
25
+ /** Set when strategy === 'label-on' — merged into every descendant issue's labelIds. */
26
+ labelId?: string;
27
+ }
28
+ /**
29
+ * Validate a stored `linearMappingStrategy` frontmatter value at the type
30
+ * boundary. Returns `undefined` for anything that isn't one of the three
31
+ * known strategies — the caller falls back to `'project'` in that case.
32
+ */
33
+ export declare function toOptionalStrategy(v: unknown): LinearMappingStrategy | undefined;
34
+ /** Resolve the epic-mapping strategy for an already-pushed epic (read-only). */
35
+ export declare function strategyFromEpic(epic: Epic, config: OpenPlanrConfig): LinearMappingStrategy;
36
+ /**
37
+ * Build the descendant-propagation context for a feature/story/tasklist push
38
+ * **without** invoking any Linear mutation. Used by granular push scopes
39
+ * (FEAT/US/TASK/QT/BL) where the epic is already mapped — the strategy is
40
+ * whatever the epic's frontmatter says it is, and the containing projectId
41
+ * + milestoneId + labelId are read-only from that frontmatter.
42
+ */
43
+ export declare function contextFromMappedEpic(epic: Epic, config: OpenPlanrConfig): StrategyContext;
44
+ /**
45
+ * Read an issue's existing labelIds from Linear so we can merge (not stomp)
46
+ * when the push re-applies the epic's label. Only called in the `label-on`
47
+ * branch, so the extra round-trip is isolated to that strategy.
48
+ */
49
+ export declare function readExistingLabelIds(client: LinearClient, issueId: string): Promise<string[]>;
50
+ /** Dedupe helper — merges `extra` into `base`, preserving order. */
51
+ export declare function mergeLabelIds(base: string[], extra: string | undefined): string[];
52
+ /**
53
+ * Idempotent team label for a given OpenPlanr artifact type. Ensures the
54
+ * label exists in Linear (creates or reuses by name), caches the result
55
+ * per-push so cascades don't call the API once per item. Used by every
56
+ * push worker to tag issues with a GitHub-style `feature` / `story` /
57
+ * `task` / `quick-task` / `backlog` label.
58
+ */
59
+ export declare function ensureTypeLabel(client: LinearClient, teamId: string, config: OpenPlanrConfig, type: LinearLabeledArtifactType): Promise<string>;
60
+ /**
61
+ * In-process cache keyed by artifact type. Avoids round-tripping
62
+ * `ensureIssueLabel` once per item in a cascade (`pushEpicScope` with many
63
+ * features / stories / tasks / QTs / BLs hits Linear once per type).
64
+ */
65
+ export declare function createTypeLabelCache(client: LinearClient, teamId: string, config: OpenPlanrConfig): (type: LinearLabeledArtifactType) => Promise<string>;
66
+ //# sourceMappingURL=strategy-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strategy-context.d.ts","sourceRoot":"","sources":["../../../src/services/linear/strategy-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,IAAI,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAI1F;;;GAGG;AACH,MAAM,MAAM,yBAAyB,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;AA0B3F,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,eAAe,EACvB,IAAI,EAAE,yBAAyB,GAC9B,MAAM,CAER;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,qBAAqB,CAAC;IAChC,4EAA4E;IAC5E,SAAS,EAAE,MAAM,CAAC;IAClB,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wFAAwF;IACxF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,OAAO,GAAG,qBAAqB,GAAG,SAAS,CAGhF;AAED,gFAAgF;AAChF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,eAAe,GAAG,qBAAqB,CAE3F;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,eAAe,GAAG,eAAe,CAS1F;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,EAAE,CAAC,CAMnB;AAED,oEAAoE;AACpE,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAIjF;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,eAAe,EACvB,IAAI,EAAE,yBAAyB,GAC9B,OAAO,CAAC,MAAM,CAAC,CASjB;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,eAAe,GACtB,CAAC,IAAI,EAAE,yBAAyB,KAAK,OAAO,CAAC,MAAM,CAAC,CAStD"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Epic-mapping strategy resolution + descendant-propagation context.
3
+ *
4
+ * `StrategyContext` is the read-only bundle that a single-scope push
5
+ * (feature / story / tasklist / QT / BL) needs to attach descendant issues
6
+ * into Linear correctly: `projectId` is always set, `milestoneId` / `labelId`
7
+ * are populated per the epic's strategy. The epic-scope push also builds a
8
+ * context on first push (interactive mapping prompt or `--as` flag) — that
9
+ * builder lives inside `pushEpicScope` because it performs Linear mutations.
10
+ */
11
+ import { ensureIssueLabel } from '../linear-service.js';
12
+ import { withLinearRetry } from './errors.js';
13
+ const DEFAULT_TYPE_LABEL_NAMES = {
14
+ feature: 'feature',
15
+ story: 'story',
16
+ task: 'task',
17
+ quick: 'quick-task',
18
+ backlog: 'backlog',
19
+ };
20
+ const TYPE_LABEL_COLORS = {
21
+ feature: '#5E6AD2', // Linear's indigo
22
+ story: '#4CB782', // green
23
+ task: '#F2994A', // orange
24
+ quick: '#BB87FC', // purple
25
+ backlog: '#888888', // grey (unchanged — matches pre-refactor default)
26
+ };
27
+ const TYPE_LABEL_DESCRIPTIONS = {
28
+ feature: 'OpenPlanr features (auto-applied by `planr linear push FEAT-*`).',
29
+ story: 'OpenPlanr user stories (auto-applied by `planr linear push US-*`).',
30
+ task: 'OpenPlanr task lists (auto-applied by `planr linear push TASK-*`).',
31
+ quick: 'OpenPlanr quick tasks (auto-applied by `planr linear push QT-*`).',
32
+ backlog: 'OpenPlanr backlog items (auto-applied by `planr linear push BL-*`).',
33
+ };
34
+ export function resolveTypeLabelName(config, type) {
35
+ return config.linear?.typeLabels?.[type] ?? DEFAULT_TYPE_LABEL_NAMES[type];
36
+ }
37
+ /**
38
+ * Validate a stored `linearMappingStrategy` frontmatter value at the type
39
+ * boundary. Returns `undefined` for anything that isn't one of the three
40
+ * known strategies — the caller falls back to `'project'` in that case.
41
+ */
42
+ export function toOptionalStrategy(v) {
43
+ if (v === 'project' || v === 'milestone-of' || v === 'label-on')
44
+ return v;
45
+ return undefined;
46
+ }
47
+ /** Resolve the epic-mapping strategy for an already-pushed epic (read-only). */
48
+ export function strategyFromEpic(epic, config) {
49
+ return epic.linearMappingStrategy ?? config.linear?.defaultEpicStrategy ?? 'project';
50
+ }
51
+ /**
52
+ * Build the descendant-propagation context for a feature/story/tasklist push
53
+ * **without** invoking any Linear mutation. Used by granular push scopes
54
+ * (FEAT/US/TASK/QT/BL) where the epic is already mapped — the strategy is
55
+ * whatever the epic's frontmatter says it is, and the containing projectId
56
+ * + milestoneId + labelId are read-only from that frontmatter.
57
+ */
58
+ export function contextFromMappedEpic(epic, config) {
59
+ const strategy = strategyFromEpic(epic, config);
60
+ const projectId = epic.linearProjectId ?? '';
61
+ return {
62
+ strategy,
63
+ projectId,
64
+ milestoneId: strategy === 'milestone-of' ? epic.linearMilestoneId : undefined,
65
+ labelId: strategy === 'label-on' ? epic.linearLabelId : undefined,
66
+ };
67
+ }
68
+ /**
69
+ * Read an issue's existing labelIds from Linear so we can merge (not stomp)
70
+ * when the push re-applies the epic's label. Only called in the `label-on`
71
+ * branch, so the extra round-trip is isolated to that strategy.
72
+ */
73
+ export async function readExistingLabelIds(client, issueId) {
74
+ return withLinearRetry('read label ids', async () => {
75
+ const issue = await client.issue(issueId);
76
+ const ids = issue?.labelIds;
77
+ return Array.isArray(ids) ? ids : [];
78
+ });
79
+ }
80
+ /** Dedupe helper — merges `extra` into `base`, preserving order. */
81
+ export function mergeLabelIds(base, extra) {
82
+ if (!extra)
83
+ return [...base];
84
+ if (base.includes(extra))
85
+ return [...base];
86
+ return [...base, extra];
87
+ }
88
+ /**
89
+ * Idempotent team label for a given OpenPlanr artifact type. Ensures the
90
+ * label exists in Linear (creates or reuses by name), caches the result
91
+ * per-push so cascades don't call the API once per item. Used by every
92
+ * push worker to tag issues with a GitHub-style `feature` / `story` /
93
+ * `task` / `quick-task` / `backlog` label.
94
+ */
95
+ export async function ensureTypeLabel(client, teamId, config, type) {
96
+ const name = resolveTypeLabelName(config, type);
97
+ const label = await ensureIssueLabel(client, {
98
+ teamId,
99
+ name,
100
+ color: TYPE_LABEL_COLORS[type],
101
+ description: TYPE_LABEL_DESCRIPTIONS[type],
102
+ });
103
+ return label.id;
104
+ }
105
+ /**
106
+ * In-process cache keyed by artifact type. Avoids round-tripping
107
+ * `ensureIssueLabel` once per item in a cascade (`pushEpicScope` with many
108
+ * features / stories / tasks / QTs / BLs hits Linear once per type).
109
+ */
110
+ export function createTypeLabelCache(client, teamId, config) {
111
+ const cache = new Map();
112
+ return (type) => {
113
+ const hit = cache.get(type);
114
+ if (hit)
115
+ return hit;
116
+ const promise = ensureTypeLabel(client, teamId, config, type);
117
+ cache.set(type, promise);
118
+ return promise;
119
+ };
120
+ }
121
+ //# sourceMappingURL=strategy-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strategy-context.js","sourceRoot":"","sources":["../../../src/services/linear/strategy-context.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Local-only Linear ↔ OpenPlanr mapping table for `planr linear status`.
3
+ */
4
+ import type { LinearMappingTableRow, OpenPlanrConfig } from '../models/types.js';
5
+ /**
6
+ * Collect mapping rows from local frontmatter only (no Linear API).
7
+ * With `scopeEpicId`, only that epic and descendants (features, stories, tasks in cascade + tasks with `featureId` in scope).
8
+ */
9
+ export declare function collectLinearMappingTable(projectDir: string, config: OpenPlanrConfig, scopeEpicId?: string): Promise<LinearMappingTableRow[]>;
10
+ export declare function formatLinearMappingTable(rows: LinearMappingTableRow[]): string;
11
+ //# sourceMappingURL=linear-mapping-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linear-mapping-service.d.ts","sourceRoot":"","sources":["../../src/services/linear-mapping-service.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AA4GjF;;;GAGG;AACH,wBAAsB,yBAAyB,CAC7C,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,eAAe,EACvB,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAqHlC;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,qBAAqB,EAAE,GAAG,MAAM,CAwB9E"}