@deskwork/core 0.9.5

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 (188) hide show
  1. package/dist/body-state.d.ts +27 -0
  2. package/dist/body-state.d.ts.map +1 -0
  3. package/dist/body-state.js +62 -0
  4. package/dist/body-state.js.map +1 -0
  5. package/dist/calendar-mutations.d.ts +124 -0
  6. package/dist/calendar-mutations.d.ts.map +1 -0
  7. package/dist/calendar-mutations.js +305 -0
  8. package/dist/calendar-mutations.js.map +1 -0
  9. package/dist/calendar.d.ts +54 -0
  10. package/dist/calendar.d.ts.map +1 -0
  11. package/dist/calendar.js +430 -0
  12. package/dist/calendar.js.map +1 -0
  13. package/dist/cli.d.ts +38 -0
  14. package/dist/cli.d.ts.map +1 -0
  15. package/dist/cli.js +72 -0
  16. package/dist/cli.js.map +1 -0
  17. package/dist/config.d.ts +91 -0
  18. package/dist/config.d.ts.map +1 -0
  19. package/dist/config.js +216 -0
  20. package/dist/config.js.map +1 -0
  21. package/dist/content-index.d.ts +74 -0
  22. package/dist/content-index.d.ts.map +1 -0
  23. package/dist/content-index.js +205 -0
  24. package/dist/content-index.js.map +1 -0
  25. package/dist/content-tree-fs-walk.d.ts +54 -0
  26. package/dist/content-tree-fs-walk.d.ts.map +1 -0
  27. package/dist/content-tree-fs-walk.js +112 -0
  28. package/dist/content-tree-fs-walk.js.map +1 -0
  29. package/dist/content-tree-helpers.d.ts +52 -0
  30. package/dist/content-tree-helpers.d.ts.map +1 -0
  31. package/dist/content-tree-helpers.js +116 -0
  32. package/dist/content-tree-helpers.js.map +1 -0
  33. package/dist/content-tree-types.d.ts +175 -0
  34. package/dist/content-tree-types.d.ts.map +1 -0
  35. package/dist/content-tree-types.js +10 -0
  36. package/dist/content-tree-types.js.map +1 -0
  37. package/dist/content-tree.d.ts +93 -0
  38. package/dist/content-tree.d.ts.map +1 -0
  39. package/dist/content-tree.js +385 -0
  40. package/dist/content-tree.js.map +1 -0
  41. package/dist/doctor/index.d.ts +11 -0
  42. package/dist/doctor/index.d.ts.map +1 -0
  43. package/dist/doctor/index.js +10 -0
  44. package/dist/doctor/index.js.map +1 -0
  45. package/dist/doctor/project-rules.d.ts +59 -0
  46. package/dist/doctor/project-rules.d.ts.map +1 -0
  47. package/dist/doctor/project-rules.js +143 -0
  48. package/dist/doctor/project-rules.js.map +1 -0
  49. package/dist/doctor/rules/calendar-uuid-missing.d.ts +19 -0
  50. package/dist/doctor/rules/calendar-uuid-missing.d.ts.map +1 -0
  51. package/dist/doctor/rules/calendar-uuid-missing.js +176 -0
  52. package/dist/doctor/rules/calendar-uuid-missing.js.map +1 -0
  53. package/dist/doctor/rules/duplicate-id.d.ts +27 -0
  54. package/dist/doctor/rules/duplicate-id.d.ts.map +1 -0
  55. package/dist/doctor/rules/duplicate-id.js +157 -0
  56. package/dist/doctor/rules/duplicate-id.js.map +1 -0
  57. package/dist/doctor/rules/legacy-top-level-id-migration.d.ts +40 -0
  58. package/dist/doctor/rules/legacy-top-level-id-migration.d.ts.map +1 -0
  59. package/dist/doctor/rules/legacy-top-level-id-migration.js +232 -0
  60. package/dist/doctor/rules/legacy-top-level-id-migration.js.map +1 -0
  61. package/dist/doctor/rules/missing-frontmatter-id.d.ts +45 -0
  62. package/dist/doctor/rules/missing-frontmatter-id.d.ts.map +1 -0
  63. package/dist/doctor/rules/missing-frontmatter-id.js +283 -0
  64. package/dist/doctor/rules/missing-frontmatter-id.js.map +1 -0
  65. package/dist/doctor/rules/orphan-frontmatter-id.d.ts +18 -0
  66. package/dist/doctor/rules/orphan-frontmatter-id.d.ts.map +1 -0
  67. package/dist/doctor/rules/orphan-frontmatter-id.js +154 -0
  68. package/dist/doctor/rules/orphan-frontmatter-id.js.map +1 -0
  69. package/dist/doctor/rules/schema-rejected.d.ts +20 -0
  70. package/dist/doctor/rules/schema-rejected.d.ts.map +1 -0
  71. package/dist/doctor/rules/schema-rejected.js +44 -0
  72. package/dist/doctor/rules/schema-rejected.js.map +1 -0
  73. package/dist/doctor/rules/slug-collision.d.ts +18 -0
  74. package/dist/doctor/rules/slug-collision.d.ts.map +1 -0
  75. package/dist/doctor/rules/slug-collision.js +65 -0
  76. package/dist/doctor/rules/slug-collision.js.map +1 -0
  77. package/dist/doctor/rules/workflow-stale.d.ts +20 -0
  78. package/dist/doctor/rules/workflow-stale.d.ts.map +1 -0
  79. package/dist/doctor/rules/workflow-stale.js +136 -0
  80. package/dist/doctor/rules/workflow-stale.js.map +1 -0
  81. package/dist/doctor/runner.d.ts +75 -0
  82. package/dist/doctor/runner.d.ts.map +1 -0
  83. package/dist/doctor/runner.js +289 -0
  84. package/dist/doctor/runner.js.map +1 -0
  85. package/dist/doctor/schema-patch.d.ts +21 -0
  86. package/dist/doctor/schema-patch.d.ts.map +1 -0
  87. package/dist/doctor/schema-patch.js +92 -0
  88. package/dist/doctor/schema-patch.js.map +1 -0
  89. package/dist/doctor/types.d.ts +185 -0
  90. package/dist/doctor/types.d.ts.map +1 -0
  91. package/dist/doctor/types.js +13 -0
  92. package/dist/doctor/types.js.map +1 -0
  93. package/dist/frontmatter.d.ts +103 -0
  94. package/dist/frontmatter.d.ts.map +1 -0
  95. package/dist/frontmatter.js +306 -0
  96. package/dist/frontmatter.js.map +1 -0
  97. package/dist/index.d.ts +27 -0
  98. package/dist/index.d.ts.map +1 -0
  99. package/dist/index.js +27 -0
  100. package/dist/index.js.map +1 -0
  101. package/dist/ingest-derive.d.ts +79 -0
  102. package/dist/ingest-derive.d.ts.map +1 -0
  103. package/dist/ingest-derive.js +299 -0
  104. package/dist/ingest-derive.js.map +1 -0
  105. package/dist/ingest-paths.d.ts +37 -0
  106. package/dist/ingest-paths.d.ts.map +1 -0
  107. package/dist/ingest-paths.js +176 -0
  108. package/dist/ingest-paths.js.map +1 -0
  109. package/dist/ingest.d.ts +162 -0
  110. package/dist/ingest.d.ts.map +1 -0
  111. package/dist/ingest.js +269 -0
  112. package/dist/ingest.js.map +1 -0
  113. package/dist/journal.d.ts +49 -0
  114. package/dist/journal.d.ts.map +1 -0
  115. package/dist/journal.js +113 -0
  116. package/dist/journal.js.map +1 -0
  117. package/dist/outline-split.d.ts +38 -0
  118. package/dist/outline-split.d.ts.map +1 -0
  119. package/dist/outline-split.js +84 -0
  120. package/dist/outline-split.js.map +1 -0
  121. package/dist/overrides.d.ts +83 -0
  122. package/dist/overrides.d.ts.map +1 -0
  123. package/dist/overrides.js +88 -0
  124. package/dist/overrides.js.map +1 -0
  125. package/dist/paths.d.ts +183 -0
  126. package/dist/paths.d.ts.map +1 -0
  127. package/dist/paths.js +266 -0
  128. package/dist/paths.js.map +1 -0
  129. package/dist/remark-image-figure.mjs +77 -0
  130. package/dist/remark-strip-first-h1.mjs +26 -0
  131. package/dist/remark-strip-outline.mjs +44 -0
  132. package/dist/rename-slug.d.ts +49 -0
  133. package/dist/rename-slug.d.ts.map +1 -0
  134. package/dist/rename-slug.js +161 -0
  135. package/dist/rename-slug.js.map +1 -0
  136. package/dist/review/handlers.d.ts +55 -0
  137. package/dist/review/handlers.d.ts.map +1 -0
  138. package/dist/review/handlers.js +307 -0
  139. package/dist/review/handlers.js.map +1 -0
  140. package/dist/review/index.d.ts +14 -0
  141. package/dist/review/index.d.ts.map +1 -0
  142. package/dist/review/index.js +13 -0
  143. package/dist/review/index.js.map +1 -0
  144. package/dist/review/journal-mappers.d.ts +35 -0
  145. package/dist/review/journal-mappers.d.ts.map +1 -0
  146. package/dist/review/journal-mappers.js +48 -0
  147. package/dist/review/journal-mappers.js.map +1 -0
  148. package/dist/review/pipeline.d.ts +79 -0
  149. package/dist/review/pipeline.d.ts.map +1 -0
  150. package/dist/review/pipeline.js +234 -0
  151. package/dist/review/pipeline.js.map +1 -0
  152. package/dist/review/render.d.ts +27 -0
  153. package/dist/review/render.d.ts.map +1 -0
  154. package/dist/review/render.js +42 -0
  155. package/dist/review/render.js.map +1 -0
  156. package/dist/review/report.d.ts +50 -0
  157. package/dist/review/report.d.ts.map +1 -0
  158. package/dist/review/report.js +164 -0
  159. package/dist/review/report.js.map +1 -0
  160. package/dist/review/result.d.ts +12 -0
  161. package/dist/review/result.d.ts.map +1 -0
  162. package/dist/review/result.js +12 -0
  163. package/dist/review/result.js.map +1 -0
  164. package/dist/review/start-handlers.d.ts +62 -0
  165. package/dist/review/start-handlers.d.ts.map +1 -0
  166. package/dist/review/start-handlers.js +223 -0
  167. package/dist/review/start-handlers.js.map +1 -0
  168. package/dist/review/types.d.ts +169 -0
  169. package/dist/review/types.d.ts.map +1 -0
  170. package/dist/review/types.js +26 -0
  171. package/dist/review/types.js.map +1 -0
  172. package/dist/review/workflow-paths.d.ts +68 -0
  173. package/dist/review/workflow-paths.d.ts.map +1 -0
  174. package/dist/review/workflow-paths.js +112 -0
  175. package/dist/review/workflow-paths.js.map +1 -0
  176. package/dist/scaffold.d.ts +67 -0
  177. package/dist/scaffold.d.ts.map +1 -0
  178. package/dist/scaffold.js +122 -0
  179. package/dist/scaffold.js.map +1 -0
  180. package/dist/scrapbook.d.ts +229 -0
  181. package/dist/scrapbook.d.ts.map +1 -0
  182. package/dist/scrapbook.js +500 -0
  183. package/dist/scrapbook.js.map +1 -0
  184. package/dist/types.d.ts +197 -0
  185. package/dist/types.d.ts.map +1 -0
  186. package/dist/types.js +120 -0
  187. package/dist/types.js.map +1 -0
  188. package/package.json +160 -0
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Classify whether a scaffolded blog post's body is still the placeholder
3
+ * or has been replaced with real prose.
4
+ *
5
+ * The scaffold produced by `scaffoldBlogPost` writes an H1, optionally a
6
+ * `## Outline` section, and a `<!-- Write your post here -->` placeholder.
7
+ * The outline is a legitimate authored artifact during Outlining; its
8
+ * presence must NOT make the body look written. This helper strips the
9
+ * outline and classifies what remains:
10
+ *
11
+ * - `missing` — file does not exist
12
+ * - `placeholder` — file exists but body (minus outline) is only the
13
+ * placeholder marker and whitespace
14
+ * - `written` — file exists and prose remains after placeholder + outline
15
+ * are accounted for
16
+ *
17
+ * Used by the review/studio surfaces to branch on whether a post has
18
+ * actually been drafted, not just scaffolded.
19
+ */
20
+ export type BodyState = 'missing' | 'placeholder' | 'written';
21
+ /** The body-placeholder marker written by scaffoldBlogPost. Exact string. */
22
+ export declare const PLACEHOLDER_MARKER = "<!-- Write your post here -->";
23
+ /**
24
+ * Classify the body of a scaffolded blog post at `filePath`.
25
+ */
26
+ export declare function bodyState(filePath: string): BodyState;
27
+ //# sourceMappingURL=body-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"body-state.d.ts","sourceRoot":"","sources":["../src/body-state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,aAAa,GAAG,SAAS,CAAC;AAE9D,6EAA6E;AAC7E,eAAO,MAAM,kBAAkB,kCAAkC,CAAC;AAoBlE;;GAEG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAkBrD"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Classify whether a scaffolded blog post's body is still the placeholder
3
+ * or has been replaced with real prose.
4
+ *
5
+ * The scaffold produced by `scaffoldBlogPost` writes an H1, optionally a
6
+ * `## Outline` section, and a `<!-- Write your post here -->` placeholder.
7
+ * The outline is a legitimate authored artifact during Outlining; its
8
+ * presence must NOT make the body look written. This helper strips the
9
+ * outline and classifies what remains:
10
+ *
11
+ * - `missing` — file does not exist
12
+ * - `placeholder` — file exists but body (minus outline) is only the
13
+ * placeholder marker and whitespace
14
+ * - `written` — file exists and prose remains after placeholder + outline
15
+ * are accounted for
16
+ *
17
+ * Used by the review/studio surfaces to branch on whether a post has
18
+ * actually been drafted, not just scaffolded.
19
+ */
20
+ import { existsSync, readFileSync } from 'node:fs';
21
+ /** The body-placeholder marker written by scaffoldBlogPost. Exact string. */
22
+ export const PLACEHOLDER_MARKER = '<!-- Write your post here -->';
23
+ /**
24
+ * Strip the `## Outline` section (heading through the next H2 or EOF)
25
+ * so its content doesn't masquerade as body prose.
26
+ */
27
+ function stripOutlineSection(body) {
28
+ const lines = body.split('\n');
29
+ const startIdx = lines.findIndex((line) => /^##[ \t]+Outline\b/.test(line));
30
+ if (startIdx < 0)
31
+ return body;
32
+ let endIdx = lines.length;
33
+ for (let i = startIdx + 1; i < lines.length; i++) {
34
+ if (/^##[ \t]+/.test(lines[i])) {
35
+ endIdx = i;
36
+ break;
37
+ }
38
+ }
39
+ return [...lines.slice(0, startIdx), ...lines.slice(endIdx)].join('\n');
40
+ }
41
+ /**
42
+ * Classify the body of a scaffolded blog post at `filePath`.
43
+ */
44
+ export function bodyState(filePath) {
45
+ if (!existsSync(filePath))
46
+ return 'missing';
47
+ const content = readFileSync(filePath, 'utf8');
48
+ // `\r?\n` mirrors `frontmatter.ts`'s FRONTMATTER_RE so files saved
49
+ // with Windows line endings classify the same way as `\n`-only files.
50
+ const fmMatch = content.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/);
51
+ const body = fmMatch ? content.slice(fmMatch[0].length) : content;
52
+ const withoutH1 = body.replace(/^\s*#[^\n]*\n?/, '');
53
+ const withoutOutline = stripOutlineSection(withoutH1);
54
+ const trimmed = withoutOutline.trim();
55
+ if (trimmed === PLACEHOLDER_MARKER)
56
+ return 'placeholder';
57
+ if (trimmed === '')
58
+ return 'placeholder';
59
+ const withoutPlaceholder = trimmed.replace(PLACEHOLDER_MARKER, '').trim();
60
+ return withoutPlaceholder.length > 0 ? 'written' : 'placeholder';
61
+ }
62
+ //# sourceMappingURL=body-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"body-state.js","sourceRoot":"","sources":["../src/body-state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAInD,6EAA6E;AAC7E,MAAM,CAAC,MAAM,kBAAkB,GAAG,+BAA+B,CAAC;AAElE;;;GAGG;AACH,SAAS,mBAAmB,CAAC,IAAY;IACvC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,QAAQ,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAC5E,IAAI,QAAQ,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAC9B,IAAI,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IAC1B,KAAK,IAAI,CAAC,GAAG,QAAQ,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACjD,IAAI,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC/B,MAAM,GAAG,CAAC,CAAC;YACX,MAAM;QACR,CAAC;IACH,CAAC;IACD,OAAO,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1E,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,SAAS,CAAC,QAAgB;IACxC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,SAAS,CAAC;IAC5C,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAE/C,mEAAmE;IACnE,sEAAsE;IACtE,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC;IACjE,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAElE,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAC;IACrD,MAAM,cAAc,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;IAEtD,MAAM,OAAO,GAAG,cAAc,CAAC,IAAI,EAAE,CAAC;IACtC,IAAI,OAAO,KAAK,kBAAkB;QAAE,OAAO,aAAa,CAAC;IACzD,IAAI,OAAO,KAAK,EAAE;QAAE,OAAO,aAAa,CAAC;IAEzC,MAAM,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC1E,OAAO,kBAAkB,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC;AACnE,CAAC"}
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Pure mutations on an in-memory EditorialCalendar.
3
+ *
4
+ * None of these touch disk — callers parse the calendar, mutate, then write
5
+ * it back. Each mutation validates stage invariants (an entry must be in
6
+ * Planned to be drafted, etc.) and throws a descriptive Error if violated.
7
+ */
8
+ import { type CalendarEntry, type ContentType, type DistributionRecord, type EditorialCalendar, type Platform } from './types.ts';
9
+ /** Convert a title to a URL-safe slug. */
10
+ export declare function slugify(title: string): string;
11
+ /** Add a new entry to the Ideas stage. */
12
+ export declare function addEntry(calendar: EditorialCalendar, title: string, opts?: {
13
+ description?: string;
14
+ source?: CalendarEntry['source'];
15
+ contentType?: ContentType;
16
+ contentUrl?: string;
17
+ /**
18
+ * Explicit slug override. Use this to capture hierarchical entries
19
+ * (e.g. "the-outbound/characters/strivers") whose slug shouldn't be
20
+ * derived from the title. When omitted, the slug is `slugify(title)`.
21
+ */
22
+ slug?: string;
23
+ }): CalendarEntry;
24
+ /**
25
+ * Find a calendar entry by its stable UUID. Prefer this over `findEntry`
26
+ * (slug lookup) anywhere the caller has an entry already and wants the
27
+ * join to survive a future slug rename.
28
+ */
29
+ export declare function findEntryById(calendar: EditorialCalendar, id: string): CalendarEntry | undefined;
30
+ /** Move an entry to Planned and set target keywords (and optionally topics). */
31
+ export declare function planEntry(calendar: EditorialCalendar, slug: string, keywords: string[], opts?: {
32
+ topics?: string[];
33
+ }): CalendarEntry;
34
+ /**
35
+ * Set or clear an entry's `contentUrl`. Used when late-setting a URL on a
36
+ * youtube or tool entry before publishing. Pass `undefined` to unset.
37
+ */
38
+ export declare function setContentUrl(calendar: EditorialCalendar, slug: string, url: string | undefined): CalendarEntry;
39
+ /**
40
+ * Move an entry to Outlining. Precondition: Planned.
41
+ *
42
+ * The outline skill scaffolds the blog file (for blog entries) and
43
+ * advances the entry to this stage; an outline review can happen
44
+ * before the entry moves on to Drafting.
45
+ */
46
+ export declare function outlineEntry(calendar: EditorialCalendar, slug: string): CalendarEntry;
47
+ /**
48
+ * Move an entry to Drafting. Precondition: Outlining (the approved
49
+ * outline is the handoff into body-drafting). The issueNumber
50
+ * argument records a previously-created GitHub issue if the caller
51
+ * opened one; the helper does not call gh itself.
52
+ */
53
+ export declare function draftEntry(calendar: EditorialCalendar, slug: string, issueNumber?: number): CalendarEntry;
54
+ /**
55
+ * Move an entry to `Paused`, recording its prior stage so `unpauseEntry`
56
+ * can restore it. Refuses to pause an already-Paused entry (would lose
57
+ * the original `pausedFrom`) and refuses to pause a `Published` entry
58
+ * (terminal; a shipped post can't be "in progress again"). See #27.
59
+ */
60
+ export declare function pauseEntry(calendar: EditorialCalendar, slug: string): CalendarEntry;
61
+ /**
62
+ * Restore a `Paused` entry to its `pausedFrom` stage. Throws if the
63
+ * entry isn't Paused, or if `pausedFrom` is missing (legacy / corrupt
64
+ * state — operator must move the entry by hand). See #27.
65
+ */
66
+ export declare function unpauseEntry(calendar: EditorialCalendar, slug: string): CalendarEntry;
67
+ /** Mark an entry Published with the given date (defaults to today). */
68
+ export declare function publishEntry(calendar: EditorialCalendar, slug: string, datePublished?: string): CalendarEntry;
69
+ /** Find an entry by slug. */
70
+ export declare function findEntry(calendar: EditorialCalendar, slug: string): CalendarEntry | undefined;
71
+ /**
72
+ * CLI-arg friendly lookup: tries `id` first (stable across slug
73
+ * renames), falls back to `slug`. Use this anywhere an operator-typed
74
+ * argument might be either form — e.g. `deskwork doctor --entry <X>`.
75
+ *
76
+ * Returns `undefined` when neither match. Empty / whitespace-only
77
+ * input also returns undefined.
78
+ */
79
+ export declare function findEntryBySlugOrId(calendar: EditorialCalendar, slugOrId: string): CalendarEntry | undefined;
80
+ /**
81
+ * Append a distribution record for a published post. The referenced entry
82
+ * must exist and be in the Published stage — we don't record shares for
83
+ * posts that haven't shipped yet.
84
+ *
85
+ * Resolves by entryId when present (stable) or falls back to slug
86
+ * (legacy callers). On success, stamps entryId onto the record so
87
+ * downstream joins use the stable identity even if the slug later
88
+ * changes.
89
+ *
90
+ * Phase 21a: `record.url` may be an empty string at creation time. The
91
+ * shortform flow `addDistribution`s a placeholder record (URL gets filled
92
+ * in by `updateDistributionUrl` once the operator has posted to the
93
+ * platform). The DistributionRecord.url type stays `string` — there is
94
+ * no runtime non-empty check, so the existing surface is unchanged.
95
+ */
96
+ export declare function addDistribution(calendar: EditorialCalendar, record: DistributionRecord): DistributionRecord;
97
+ /**
98
+ * Set / update the URL on a distribution record. Used after the operator
99
+ * has manually posted a shortform to the platform and obtained the share
100
+ * URL.
101
+ *
102
+ * Match precedence: `entryId` (stable) → `(slug, platform, channel?)`
103
+ * (legacy fallback). When no record exists yet the helper creates one via
104
+ * `addDistribution` so the URL becomes the first thing recorded for that
105
+ * distribution.
106
+ *
107
+ * Channel comparison is case-insensitive (matches the existing approve
108
+ * helper's behavior). Pass `channel: undefined` to match a record that
109
+ * has no channel set.
110
+ *
111
+ * Only fields explicitly supplied are written — leaving `notes`
112
+ * undefined preserves any existing note. `dateShared` defaults to today
113
+ * (UTC) when omitted, both for new records and as the explicit set when
114
+ * updating an existing one.
115
+ *
116
+ * @returns the updated DistributionRecord (always present after this call).
117
+ */
118
+ export declare function updateDistributionUrl(calendar: EditorialCalendar, match: {
119
+ entryId?: string;
120
+ slug?: string;
121
+ platform: Platform;
122
+ channel?: string;
123
+ }, url: string, dateShared?: string, notes?: string): DistributionRecord;
124
+ //# sourceMappingURL=calendar-mutations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"calendar-mutations.d.ts","sourceRoot":"","sources":["../src/calendar-mutations.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,305 @@
1
+ /**
2
+ * Pure mutations on an in-memory EditorialCalendar.
3
+ *
4
+ * None of these touch disk — callers parse the calendar, mutate, then write
5
+ * it back. Each mutation validates stage invariants (an entry must be in
6
+ * Planned to be drafted, etc.) and throws a descriptive Error if violated.
7
+ */
8
+ import { randomUUID } from 'node:crypto';
9
+ import { isPausable, } from "./types.js";
10
+ /** Convert a title to a URL-safe slug. */
11
+ export function slugify(title) {
12
+ return title
13
+ .toLowerCase()
14
+ .replace(/[^a-z0-9]+/g, '-')
15
+ .replace(/^-|-$/g, '');
16
+ }
17
+ /** Add a new entry to the Ideas stage. */
18
+ export function addEntry(calendar, title, opts) {
19
+ const slug = opts?.slug ?? slugify(title);
20
+ const existing = calendar.entries.find((e) => e.slug === slug);
21
+ if (existing) {
22
+ throw new Error(`Entry with slug "${slug}" already exists in stage "${existing.stage}"`);
23
+ }
24
+ const entry = {
25
+ id: randomUUID(),
26
+ slug,
27
+ title,
28
+ description: opts?.description ?? '',
29
+ stage: 'Ideas',
30
+ targetKeywords: [],
31
+ source: opts?.source ?? 'manual',
32
+ };
33
+ if (opts?.contentType !== undefined)
34
+ entry.contentType = opts.contentType;
35
+ if (opts?.contentUrl !== undefined && opts.contentUrl.length > 0) {
36
+ entry.contentUrl = opts.contentUrl;
37
+ }
38
+ calendar.entries.push(entry);
39
+ return entry;
40
+ }
41
+ /**
42
+ * Find a calendar entry by its stable UUID. Prefer this over `findEntry`
43
+ * (slug lookup) anywhere the caller has an entry already and wants the
44
+ * join to survive a future slug rename.
45
+ */
46
+ export function findEntryById(calendar, id) {
47
+ if (!id)
48
+ return undefined;
49
+ return calendar.entries.find((e) => e.id === id);
50
+ }
51
+ /** Move an entry to Planned and set target keywords (and optionally topics). */
52
+ export function planEntry(calendar, slug, keywords, opts) {
53
+ const entry = calendar.entries.find((e) => e.slug === slug);
54
+ if (!entry) {
55
+ throw new Error(`No calendar entry found with slug: ${slug}`);
56
+ }
57
+ if (entry.stage !== 'Ideas') {
58
+ throw new Error(`Entry "${slug}" is in stage "${entry.stage}" — must be in Ideas to plan`);
59
+ }
60
+ entry.stage = 'Planned';
61
+ entry.targetKeywords = keywords;
62
+ if (opts?.topics !== undefined && opts.topics.length > 0) {
63
+ entry.topics = opts.topics;
64
+ }
65
+ return entry;
66
+ }
67
+ /**
68
+ * Set or clear an entry's `contentUrl`. Used when late-setting a URL on a
69
+ * youtube or tool entry before publishing. Pass `undefined` to unset.
70
+ */
71
+ export function setContentUrl(calendar, slug, url) {
72
+ const entry = calendar.entries.find((e) => e.slug === slug);
73
+ if (!entry) {
74
+ throw new Error(`No calendar entry found with slug: ${slug}`);
75
+ }
76
+ if (url === undefined || url.length === 0) {
77
+ delete entry.contentUrl;
78
+ }
79
+ else {
80
+ entry.contentUrl = url;
81
+ }
82
+ return entry;
83
+ }
84
+ /**
85
+ * Move an entry to Outlining. Precondition: Planned.
86
+ *
87
+ * The outline skill scaffolds the blog file (for blog entries) and
88
+ * advances the entry to this stage; an outline review can happen
89
+ * before the entry moves on to Drafting.
90
+ */
91
+ export function outlineEntry(calendar, slug) {
92
+ const entry = calendar.entries.find((e) => e.slug === slug);
93
+ if (!entry) {
94
+ throw new Error(`No calendar entry found with slug: ${slug}`);
95
+ }
96
+ if (entry.stage !== 'Planned') {
97
+ throw new Error(`Entry "${slug}" is in stage "${entry.stage}" — must be in Planned to outline`);
98
+ }
99
+ entry.stage = 'Outlining';
100
+ return entry;
101
+ }
102
+ /**
103
+ * Move an entry to Drafting. Precondition: Outlining (the approved
104
+ * outline is the handoff into body-drafting). The issueNumber
105
+ * argument records a previously-created GitHub issue if the caller
106
+ * opened one; the helper does not call gh itself.
107
+ */
108
+ export function draftEntry(calendar, slug, issueNumber) {
109
+ const entry = calendar.entries.find((e) => e.slug === slug);
110
+ if (!entry) {
111
+ throw new Error(`No calendar entry found with slug: ${slug}`);
112
+ }
113
+ if (entry.stage !== 'Outlining') {
114
+ throw new Error(`Entry "${slug}" is in stage "${entry.stage}" — must be in Outlining to draft`);
115
+ }
116
+ entry.stage = 'Drafting';
117
+ if (issueNumber !== undefined) {
118
+ entry.issueNumber = issueNumber;
119
+ }
120
+ return entry;
121
+ }
122
+ /**
123
+ * Move an entry to `Paused`, recording its prior stage so `unpauseEntry`
124
+ * can restore it. Refuses to pause an already-Paused entry (would lose
125
+ * the original `pausedFrom`) and refuses to pause a `Published` entry
126
+ * (terminal; a shipped post can't be "in progress again"). See #27.
127
+ */
128
+ export function pauseEntry(calendar, slug) {
129
+ const entry = calendar.entries.find((e) => e.slug === slug);
130
+ if (!entry) {
131
+ throw new Error(`No calendar entry found with slug: ${slug}`);
132
+ }
133
+ if (entry.stage === 'Paused') {
134
+ throw new Error(`Entry "${slug}" is already Paused.`);
135
+ }
136
+ if (!isPausable(entry.stage)) {
137
+ throw new Error(`Entry "${slug}" is in stage "${entry.stage}" — only non-terminal stages (Ideas / Planned / Outlining / Drafting / Review) can be paused.`);
138
+ }
139
+ entry.pausedFrom = entry.stage;
140
+ entry.stage = 'Paused';
141
+ return entry;
142
+ }
143
+ /**
144
+ * Restore a `Paused` entry to its `pausedFrom` stage. Throws if the
145
+ * entry isn't Paused, or if `pausedFrom` is missing (legacy / corrupt
146
+ * state — operator must move the entry by hand). See #27.
147
+ */
148
+ export function unpauseEntry(calendar, slug) {
149
+ const entry = calendar.entries.find((e) => e.slug === slug);
150
+ if (!entry) {
151
+ throw new Error(`No calendar entry found with slug: ${slug}`);
152
+ }
153
+ if (entry.stage !== 'Paused') {
154
+ throw new Error(`Entry "${slug}" is in stage "${entry.stage}" — only Paused entries can be resumed.`);
155
+ }
156
+ if (entry.pausedFrom === undefined) {
157
+ throw new Error(`Entry "${slug}" is Paused but has no pausedFrom — cannot resume automatically. Edit the calendar by hand to move it back to the right stage.`);
158
+ }
159
+ entry.stage = entry.pausedFrom;
160
+ delete entry.pausedFrom;
161
+ return entry;
162
+ }
163
+ /** Mark an entry Published with the given date (defaults to today). */
164
+ export function publishEntry(calendar, slug, datePublished) {
165
+ const entry = calendar.entries.find((e) => e.slug === slug);
166
+ if (!entry) {
167
+ throw new Error(`No calendar entry found with slug: ${slug}`);
168
+ }
169
+ entry.stage = 'Published';
170
+ entry.datePublished =
171
+ datePublished ?? new Date().toISOString().slice(0, 10);
172
+ return entry;
173
+ }
174
+ /** Find an entry by slug. */
175
+ export function findEntry(calendar, slug) {
176
+ return calendar.entries.find((e) => e.slug === slug);
177
+ }
178
+ /**
179
+ * CLI-arg friendly lookup: tries `id` first (stable across slug
180
+ * renames), falls back to `slug`. Use this anywhere an operator-typed
181
+ * argument might be either form — e.g. `deskwork doctor --entry <X>`.
182
+ *
183
+ * Returns `undefined` when neither match. Empty / whitespace-only
184
+ * input also returns undefined.
185
+ */
186
+ export function findEntryBySlugOrId(calendar, slugOrId) {
187
+ if (slugOrId === undefined || slugOrId === null)
188
+ return undefined;
189
+ const trimmed = slugOrId.trim();
190
+ if (trimmed === '')
191
+ return undefined;
192
+ return findEntryById(calendar, trimmed) ?? findEntry(calendar, trimmed);
193
+ }
194
+ /**
195
+ * Append a distribution record for a published post. The referenced entry
196
+ * must exist and be in the Published stage — we don't record shares for
197
+ * posts that haven't shipped yet.
198
+ *
199
+ * Resolves by entryId when present (stable) or falls back to slug
200
+ * (legacy callers). On success, stamps entryId onto the record so
201
+ * downstream joins use the stable identity even if the slug later
202
+ * changes.
203
+ *
204
+ * Phase 21a: `record.url` may be an empty string at creation time. The
205
+ * shortform flow `addDistribution`s a placeholder record (URL gets filled
206
+ * in by `updateDistributionUrl` once the operator has posted to the
207
+ * platform). The DistributionRecord.url type stays `string` — there is
208
+ * no runtime non-empty check, so the existing surface is unchanged.
209
+ */
210
+ export function addDistribution(calendar, record) {
211
+ const entry = (record.entryId && calendar.entries.find((e) => e.id === record.entryId)) ||
212
+ calendar.entries.find((e) => e.slug === record.slug);
213
+ if (!entry) {
214
+ throw new Error(`No calendar entry found with entryId "${record.entryId ?? ''}" or slug "${record.slug}"`);
215
+ }
216
+ if (entry.stage !== 'Published') {
217
+ throw new Error(`Entry "${entry.slug}" is in stage "${entry.stage}" — must be Published to record a distribution`);
218
+ }
219
+ if (entry.id !== undefined)
220
+ record.entryId = entry.id;
221
+ record.slug = entry.slug;
222
+ calendar.distributions.push(record);
223
+ return record;
224
+ }
225
+ /**
226
+ * Set / update the URL on a distribution record. Used after the operator
227
+ * has manually posted a shortform to the platform and obtained the share
228
+ * URL.
229
+ *
230
+ * Match precedence: `entryId` (stable) → `(slug, platform, channel?)`
231
+ * (legacy fallback). When no record exists yet the helper creates one via
232
+ * `addDistribution` so the URL becomes the first thing recorded for that
233
+ * distribution.
234
+ *
235
+ * Channel comparison is case-insensitive (matches the existing approve
236
+ * helper's behavior). Pass `channel: undefined` to match a record that
237
+ * has no channel set.
238
+ *
239
+ * Only fields explicitly supplied are written — leaving `notes`
240
+ * undefined preserves any existing note. `dateShared` defaults to today
241
+ * (UTC) when omitted, both for new records and as the explicit set when
242
+ * updating an existing one.
243
+ *
244
+ * @returns the updated DistributionRecord (always present after this call).
245
+ */
246
+ export function updateDistributionUrl(calendar, match, url, dateShared, notes) {
247
+ if (!match.platform) {
248
+ throw new Error('updateDistributionUrl: platform is required');
249
+ }
250
+ if ((match.entryId === undefined || match.entryId === '') &&
251
+ (match.slug === undefined || match.slug === '')) {
252
+ throw new Error('updateDistributionUrl: entryId or slug is required');
253
+ }
254
+ const channelKey = match.channel?.toLowerCase();
255
+ const channelMatches = (recChannel) => {
256
+ if (channelKey === undefined)
257
+ return !recChannel;
258
+ return (recChannel?.toLowerCase() ?? '') === channelKey;
259
+ };
260
+ // Match by entryId first (stable across slug renames) then by
261
+ // (slug, platform, channel?) as the legacy fallback.
262
+ const existing = calendar.distributions.find((d) => {
263
+ if (d.platform !== match.platform)
264
+ return false;
265
+ if (!channelMatches(d.channel))
266
+ return false;
267
+ if (match.entryId !== undefined && match.entryId !== '' && d.entryId === match.entryId) {
268
+ return true;
269
+ }
270
+ if (match.slug !== undefined && match.slug !== '' && d.slug === match.slug) {
271
+ return true;
272
+ }
273
+ return false;
274
+ });
275
+ const today = new Date().toISOString().slice(0, 10);
276
+ if (existing) {
277
+ existing.url = url;
278
+ existing.dateShared = dateShared ?? existing.dateShared ?? today;
279
+ if (notes !== undefined) {
280
+ existing.notes = notes;
281
+ }
282
+ return existing;
283
+ }
284
+ // No record yet — fall through to addDistribution so the entry's
285
+ // Published-stage invariant is enforced and entryId/slug get stamped
286
+ // from the calendar entry.
287
+ const slug = match.slug ?? '';
288
+ const record = {
289
+ slug,
290
+ platform: match.platform,
291
+ url,
292
+ dateShared: dateShared ?? today,
293
+ };
294
+ if (match.entryId !== undefined && match.entryId !== '') {
295
+ record.entryId = match.entryId;
296
+ }
297
+ if (match.channel !== undefined && match.channel !== '') {
298
+ record.channel = match.channel;
299
+ }
300
+ if (notes !== undefined) {
301
+ record.notes = notes;
302
+ }
303
+ return addDistribution(calendar, record);
304
+ }
305
+ //# sourceMappingURL=calendar-mutations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"calendar-mutations.js","sourceRoot":"","sources":["../src/calendar-mutations.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Markdown editorial calendar parser and writer.
3
+ *
4
+ * Each site's calendar is a human-readable markdown file with one table per
5
+ * stage. This module round-trips between that format and the in-memory
6
+ * EditorialCalendar type.
7
+ *
8
+ * ## Markdown format
9
+ *
10
+ * ```markdown
11
+ * # Editorial Calendar
12
+ *
13
+ * ## Ideas
14
+ *
15
+ * | UUID | Slug | Title | Description | Keywords | Source |
16
+ * |------|------|-------|-------------|----------|--------|
17
+ * | abc-123 | my-post | My Post | A post about things | kw1, kw2 | manual |
18
+ *
19
+ * ## Planned
20
+ * ...
21
+ *
22
+ * ## Published
23
+ *
24
+ * | UUID | Slug | Title | Description | Keywords | Source | Published | Issue |
25
+ * |------|------|-------|-------------|----------|--------|-----------|-------|
26
+ * ```
27
+ *
28
+ * Optional columns (Topics, Type, URL) are emitted only when any entry uses
29
+ * them, so a calendar with no cross-posting stays visually simple. Published
30
+ * entries always include Published and Issue columns. The UUID column is
31
+ * always emitted at render time — pre-UUID calendars get backfilled lazily
32
+ * (parser assigns missing UUIDs in-memory; the next write persists them).
33
+ */
34
+ import { PLATFORMS, STAGES, type EditorialCalendar } from './types.ts';
35
+ /** Parse the editorial calendar markdown file into an EditorialCalendar. */
36
+ export declare function parseCalendar(markdown: string): EditorialCalendar;
37
+ /**
38
+ * Read and parse the editorial calendar from an absolute path.
39
+ *
40
+ * A non-existent file is treated as a logically empty calendar — the user
41
+ * hasn't written anything yet. Any other error (e.g. unreadable, malformed)
42
+ * propagates so the operator sees the problem instead of a silently-empty
43
+ * dashboard.
44
+ */
45
+ export declare function readCalendar(calendarPath: string): EditorialCalendar;
46
+ /** Render the full editorial calendar as markdown. */
47
+ export declare function renderCalendar(calendar: EditorialCalendar): string;
48
+ /** Write the editorial calendar to an absolute path. */
49
+ export declare function writeCalendar(calendarPath: string, calendar: EditorialCalendar): void;
50
+ /** Render an empty calendar — used by the install skill to seed a new file. */
51
+ export declare function renderEmptyCalendar(): string;
52
+ /** Suppress unused-import warnings for re-exported constants available to callers. */
53
+ export { PLATFORMS, STAGES };
54
+ //# sourceMappingURL=calendar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"calendar.d.ts","sourceRoot":"","sources":["../src/calendar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAIH,OAAO,EACL,SAAS,EACT,MAAM,EAQN,KAAK,iBAAiB,EAEvB,MAAM,YAAY,CAAC;AAsMpB,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,iBAAiB,CAoEjE;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,iBAAiB,CAWpE;AAyFD,sDAAsD;AACtD,wBAAgB,cAAc,CAAC,QAAQ,EAAE,iBAAiB,GAAG,MAAM,CAqClE;AAED,wDAAwD;AACxD,wBAAgB,aAAa,CAC3B,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,iBAAiB,GAC1B,IAAI,CAEN;AAED,+EAA+E;AAC/E,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED,sFAAsF;AACtF,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC"}