refactor(linkedin): merge publish into calendar — reconcile hook refs (S16)

Step 17 of voyage-build (S16 in plan). publish.md absorbed into calendar.md
as an inline action (Mark as Published flow: queue update, state update,
first-hour battle plan) reusing the same queue-manager.mjs + state-updater.mjs
primitives that publish.md called. calendar.md frontmatter triggers extended
with the publish trigger words; quick-routing block jumps straight to the
publish action when the user prompt names it.

All 21 route-refs reconciled across the 9 expected files, with the 9
hook-script refs (5 in session-start.mjs, 2 in posting-reminder.mjs, 1 in
user-prompt-context.mjs, 1 in hooks/prompts/state-update-reminder.md)
rewritten to call /linkedin:calendar so the runtime guidance no longer
points at a dead command. compile-hooks.py --check reports clean (no
type: prompt hook changes touched hooks.json).

Verify (intent: zero stray refs, file gone): exit 0. Literal Verify in
plan.md:727 logged exit 1 (same exit-inverted && pattern as S15 plan.md:635
— logged for plan-pass at Step 21).

Manifest audit: PASS (expected_paths=calendar.md present; must_contain
[Pp]ublish: 17 matches in calendar.md).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-05-28 05:27:36 +02:00
commit e4aa5a61c2
12 changed files with 117 additions and 156 deletions

View file

@ -47,8 +47,7 @@ All content commands (post, quick, react, pipeline, first-post, video, multiplat
| `/linkedin:quick` | 5-minute quick post (3-line formula) + 8 post-type templates |
| `/linkedin:pipeline` | Full end-to-end content pipeline |
| `/linkedin:batch` | Create a full week of content |
| `/linkedin:calendar` | View/manage post scheduling queue |
| `/linkedin:publish` | Mark scheduled posts as published |
| `/linkedin:calendar` | View/manage post scheduling queue + publish action (mark scheduled posts as published) |
| `/linkedin:carousel` | Structured multi-slide carousel generator |
| `/linkedin:video` | Video script generator (30s-2min) |
| `/linkedin:multiplatform` | Adapt content for other platforms |

View file

@ -142,8 +142,7 @@ All 26 commands use colon notation: `/linkedin:post`, `/linkedin:quick`, etc.
| `/linkedin:quick` | 5-minute quick post using the 3-line formula. Target: 150-500 characters. Best for reactions, observations, tips, and questions. Also the single entry point for the 8 post-type templates (fill-in-the-blank structures). |
| `/linkedin:pipeline` | Full end-to-end content pipeline from idea to published post. Guides through ideation, drafting, optimization, scheduling, pre-engagement, publishing, and post-analysis. |
| `/linkedin:batch` | Create a full week of LinkedIn content in one session. Input one theme, output 3-5 posts with varying angles and formats. Writes to scheduling queue. |
| `/linkedin:calendar` | View and manage the post scheduling queue — upcoming, overdue, published. |
| `/linkedin:publish` | Mark scheduled posts as published. Updates state and streak tracking. |
| `/linkedin:calendar` | View and manage the post scheduling queue — upcoming, overdue, published — and run the publish action (mark a scheduled post as published, update state + streak tracking, surface the first-hour engagement plan). |
| `/linkedin:video` | Video script generator for 30s, 60s, 90s, or 2-minute LinkedIn videos with pacing and visual cues. |
| `/linkedin:multiplatform` | Adapt LinkedIn content for Twitter/X threads, newsletter sections, blog posts, presentation slides, and YouTube scripts. |
| `/linkedin:react` | URL-to-post pipeline — paste an article, research paper, or news link and generate a reaction post. |
@ -503,7 +502,7 @@ Scheduled posts are tracked in `assets/drafts/queue.json`:
- Status flow: `draft` -> `scheduled` -> `published` (or `cancelled`)
- Created by `/linkedin:batch` and `/linkedin:pipeline`
- Viewed via `/linkedin:calendar`
- Transitioned via `/linkedin:publish`
- Transitioned (marked as published) via the `/linkedin:calendar` publish action
---
@ -516,7 +515,7 @@ Scheduled posts are tracked in `assets/drafts/queue.json`:
| Engagement automation | Automated commenting violates LinkedIn ToS | Manual engagement guided by `/linkedin:collab` |
| Profile editing | Plugin generates recommendations, not API calls | Apply changes manually on LinkedIn |
| Team/multi-user workflows | Designed for individual thought leaders | Enterprise LinkedIn tools |
| Content scheduling via API | No official scheduling API | Queue management with manual posting via `/linkedin:publish` |
| Content scheduling via API | No official scheduling API | Queue management with manual posting via `/linkedin:calendar` (publish action) |
---

View file

@ -123,7 +123,7 @@ After saving each draft, add it to the queue:
node --input-type=module -e "import { queueAdd } from '${CLAUDE_PLUGIN_ROOT}/hooks/scripts/queue-manager.mjs'; console.log(queueAdd('[YYYY-WXX-day-topic-slug]', 'assets/drafts/week-[WXX]/[day]-[topic-slug].md', '[YYYY-MM-DD]', '[HH:MM]', '[pillar]', '[format]', '[hook preview first 50 chars]', [character_count]));"
```
This ensures the post appears in `/linkedin:calendar`, session-start reminders, and `/linkedin:publish`.
This ensures the post appears in `/linkedin:calendar` (both for viewing and for the publish action) and in session-start reminders.
## Step 4: Review All
@ -152,7 +152,7 @@ Ask if they want to:
After approval:
- Confirm all drafts are saved and queued
- Update state file with planned topics (note: state updates for batch posts happen at publish time via `/linkedin:publish`, not at batch creation)
- Update state file with planned topics (note: state updates for batch posts happen at publish time via the `/linkedin:calendar` publish action, not at batch creation)
- Show queue summary:
```
@ -162,8 +162,7 @@ Queue Summary: [X] posts scheduled
- [Date] [Time]: "[hook preview]" — [pillar] ([format])
- [Date] [Time]: "[hook preview]" — [pillar] ([format])
View full schedule: /linkedin:calendar
Mark as published: /linkedin:publish
View full schedule + mark as published: /linkedin:calendar
Remember: Run 5x5x5 engagement 15 min before each post!
```

View file

@ -2,19 +2,26 @@
name: linkedin:calendar
description: |
View and manage your post scheduling queue. Shows next 14 days of scheduled posts,
format mix, pillar balance, and allows rescheduling or cancellation.
format mix, pillar balance, and runs the publish action (mark a scheduled post
as published, update queue + state, first-hour engagement plan).
Triggers on: "calendar", "schedule", "queue", "upcoming posts", "what's scheduled",
"show queue", "my schedule", "content calendar".
"show queue", "my schedule", "content calendar", "publish", "mark as published",
"posted today", "just published", "published a post", "post is live".
allowed-tools:
- Read
- Bash
- Write
- Edit
- AskUserQuestion
---
# LinkedIn Content Calendar
You are a LinkedIn content calendar manager. Show the user their upcoming scheduled posts and help them manage the queue.
You are a LinkedIn content calendar manager. Show the user their upcoming scheduled posts, help them manage the queue, and run the **publish action** when a scheduled post goes live.
## Quick Routing
If the user's prompt mentions "publish", "mark as published", "posted today", "just published", or "post is live", jump straight to **Step 3 — Action: Mark as Published** (skip the full calendar view). Otherwise start at Step 1.
## Step 1: Load Queue
@ -72,20 +79,91 @@ OVERDUE:
Use AskUserQuestion:
1. **Reschedule a post** — Move a post to a different date/time
2. **Cancel a post** — Remove from queue (set status to "cancelled")
3. **Mark as published** — Quick route to `/linkedin:publish`
1. **Mark as published** — A scheduled post is live; update queue + state + show first-hour plan
2. **Reschedule a post** — Move a post to a different date/time
3. **Cancel a post** — Remove from queue (set status to "cancelled")
4. **View a draft** — Read the full draft content
5. **Looks good** — No changes needed
### Reschedule Flow
### Action: Mark as Published
This is the publish flow (no separate command — runs inline here).
**3a. Show publishable posts.** Present today's scheduled posts and any overdue posts:
```
Today's Scheduled Posts:
1. "[hook preview]" — [pillar] ([format]) — Scheduled for [time]
2. "[hook preview]" — [pillar] ([format]) — Scheduled for [time]
Overdue (should have been posted):
3. "[hook preview]" — [pillar] — Was scheduled for [date]
```
If no posts are scheduled and none overdue:
```
No posts scheduled for today.
- Run /linkedin:batch to schedule content
- Run /linkedin:quick for an unplanned quick post
```
**3b. Pick a post.** Use AskUserQuestion to ask which post was published (show the list above).
**3c. Update queue status:**
```bash
node --input-type=module -e "import { queueUpdateStatus } from '${CLAUDE_PLUGIN_ROOT}/hooks/scripts/queue-manager.mjs'; console.log(queueUpdateStatus('[post-id]', 'published'));"
```
**3d. Update state file deterministically:**
```bash
node --input-type=module -e "
import { writeState, updatePostTracking } from '${CLAUDE_PLUGIN_ROOT}/hooks/scripts/state-updater.mjs';
writeState(content => updatePostTracking(content, {
postDate: 'YYYY-MM-DD',
postTopic: 'topic_area',
hookText: 'Hook text here...',
charCount: NNNN,
format: 'FORMAT'
}));
"
```
Replace placeholders with actual post data from the published post.
**3e. First-hour battle plan.** Show after marking:
```
Post marked as published! Here's your first-hour plan:
Pre-Post (if not done):
- [ ] Complete 5x5x5 engagement (15-20 min before posting)
First Hour:
- [ ] Respond to comments within 5 minutes
- [ ] Add value in every response (not just "thanks!")
- [ ] Ask follow-up questions to deepen conversation
- [ ] Target: 15+ engagements in first 60 minutes
- [ ] Check back at 30-min and 60-min marks
48-Hour Check-In:
- Run /linkedin:analyze after 48 hours to review performance
- Or use post-feedback-monitor agent for real-time tracking
```
**3f. Ask about more.** Use AskUserQuestion:
1. **Mark another post** — I published more than one (loop back to 3a)
2. **View calendar** — Show remaining schedule (loop back to Step 2)
3. **Done** — All set for now
### Action: Reschedule
If they choose to reschedule:
1. Ask which post (by number or hook preview)
2. Ask for new date and time
3. Update queue.json via queue_update_status + queue_add with new date
4. Show updated calendar
### Cancel Flow
### Action: Cancel
If they choose to cancel:
1. Ask which post
2. Confirm cancellation
@ -94,7 +172,8 @@ If they choose to cancel:
node --input-type=module -e "import { queueUpdateStatus } from '${CLAUDE_PLUGIN_ROOT}/hooks/scripts/queue-manager.mjs'; console.log(queueUpdateStatus('[post-id]', 'cancelled'));"
```
### View Draft Flow
### Action: View Draft
If they want to see a draft:
1. Ask which post
2. Read the draft file from the `draft_path`
@ -102,7 +181,7 @@ If they want to see a draft:
## Step 4: Balance Analysis
After showing the calendar, provide brief analysis:
After showing the calendar (or after a publish action loops back), provide brief analysis:
- **Format diversity**: Are formats varied enough? Flag if >2 consecutive same format.
- **Pillar balance**: Are pillars well-distributed? Flag if any pillar >50%.
@ -112,4 +191,5 @@ After showing the calendar, provide brief analysis:
## Reference Files
- `${CLAUDE_PLUGIN_ROOT}/references/scheduling-strategy.md`
- `${CLAUDE_PLUGIN_ROOT}/references/engagement-frameworks.md`
- `${CLAUDE_PLUGIN_ROOT}/assets/drafts/queue.json`

View file

@ -51,7 +51,7 @@ Upcoming Posts (next 7 days):
If there are overdue posts, display with warning:
```
OVERDUE (should have been posted):
[date]: "[hook preview]" — Run /linkedin:publish to update or /linkedin:calendar to reschedule
[date]: "[hook preview]" — Run /linkedin:calendar to mark as published or reschedule
```
If queue is empty: "No posts scheduled. Run /linkedin:batch to plan your week."
@ -80,8 +80,7 @@ Present these options to the user:
| `/linkedin:video` | Create video scripts with hook, body, CTA, captions, and thumbnail suggestions |
| `/linkedin:newsletter` | Long-form orchestrator — newsletter editions, essays, series articles (research → draft → fact-check → persona-review → lock → delivery). The single long-form entry point |
| `/linkedin:batch` | Create a full week of content in one session |
| `/linkedin:calendar` | View and manage your post scheduling queue |
| `/linkedin:publish` | Mark scheduled posts as published |
| `/linkedin:calendar` | View and manage your post scheduling queue + run the publish action (mark a scheduled post as published) |
### Strategy & Optimization
@ -164,7 +163,7 @@ If the user's intent is clear from context:
- Mentions "pipeline" or "end to end" → Route to `/linkedin:pipeline`
- Mentions "batch" or "week of content" → Route to `/linkedin:batch`
- Mentions "calendar" or "schedule" or "queue" or "upcoming posts" or "what's scheduled" → Route to `/linkedin:calendar`
- Mentions "publish" or "mark as published" or "posted today" or "just published" or "post is live" → Route to `/linkedin:publish`
- Mentions "publish" or "mark as published" or "posted today" or "just published" or "post is live" → Route to `/linkedin:calendar` (publish action)
- Mentions "plan" → Suggest `content-planner` agent
- Mentions "profile" or "360Brew" → Route to `/linkedin:profile`
- Mentions "not working" or "low reach" → Route to `/linkedin:analyze`

View file

@ -705,9 +705,10 @@ Next: Step 10 — Scheduling.
## Step 10: Scheduling — register the edition in the plugin queue
The locked, conversion-passed edition is ready to ship. Register it in the
plugin's native scheduling queue so it shows up in `/linkedin:calendar`,
`/linkedin:publish`, and the posting-time reminders — the same queue the
short-form pipeline uses. The edition is now a first-class scheduled post.
plugin's native scheduling queue so it shows up in `/linkedin:calendar`
(both for viewing and for the publish action) and the posting-time
reminders — the same queue the short-form pipeline uses. The edition is
now a first-class scheduled post.
**Procedure:**
@ -732,8 +733,8 @@ short-form pipeline uses. The edition is now a first-class scheduled post.
3. **Persist + close the edition.** Set the article's `status: "scheduled"`,
`scheduled: "<YYYY-MM-DD HH:MM>"`, and `currentPhase: "scheduling"` in
`edition-state.json`. Append a closing "edition scheduled → ready for
`/linkedin:publish` on <date>" pointer to the HANDOVER §6. The pipeline is
`edition-state.json`. Append a closing "edition scheduled → mark live via
`/linkedin:calendar` on <date>" pointer to the HANDOVER §6. The pipeline is
complete.
```
@ -741,7 +742,7 @@ Scheduling.
- Queue entry: <series-slug>-NN → assets/drafts/queue.json (status: scheduled)
- Slot: YYYY-MM-DD HH:MM format: newsletter
- Article status: scheduled
Edition complete. Visible in /linkedin:calendar; mark live with /linkedin:publish.
Edition complete. Visible in /linkedin:calendar; mark live via /linkedin:calendar (publish action).
```
---

View file

@ -1,115 +0,0 @@
---
name: linkedin:publish
description: |
Mark a scheduled post as published and update tracking state. Shows today's scheduled
posts, lets user pick which to mark as published, updates queue and state file.
Triggers on: "publish", "mark as published", "posted today", "just published",
"published a post", "post is live".
allowed-tools:
- Read
- Bash
- Write
- Edit
- AskUserQuestion
---
# LinkedIn Publish Tracker
You are a LinkedIn publish tracker. Help the user mark scheduled posts as published and keep their state up to date.
## Step 1: Load Today's Queue
```bash
node --input-type=module -e "
import { queueToday, queueOverdue, queueFormatSummary } from '${CLAUDE_PLUGIN_ROOT}/hooks/scripts/queue-manager.mjs';
console.log('=== TODAY ===');
console.log(queueFormatSummary(queueToday()));
console.log('=== OVERDUE ===');
console.log(queueFormatSummary(queueOverdue()));
"
```
Also read state: `~/.claude/linkedin-thought-leadership.local.md`
## Step 2: Show Publishable Posts
Present today's scheduled posts and any overdue posts:
```
Today's Scheduled Posts:
1. "[hook preview]" — [pillar] ([format]) — Scheduled for [time]
2. "[hook preview]" — [pillar] ([format]) — Scheduled for [time]
Overdue (should have been posted):
3. "[hook preview]" — [pillar] — Was scheduled for [date]
```
If no posts are scheduled for today and none overdue:
```
No posts scheduled for today.
- Run /linkedin:batch to schedule content
- Run /linkedin:calendar to view your schedule
- Run /linkedin:quick for an unplanned quick post
```
## Step 3: Select Post to Mark
Use AskUserQuestion to ask which post was published (show the list from Step 2).
## Step 4: Mark as Published
Update queue status:
```bash
node --input-type=module -e "import { queueUpdateStatus } from '${CLAUDE_PLUGIN_ROOT}/hooks/scripts/queue-manager.mjs'; console.log(queueUpdateStatus('[post-id]', 'published'));"
```
## Step 5: Update State File
Update state deterministically:
```bash
node --input-type=module -e "
import { writeState, updatePostTracking } from '${CLAUDE_PLUGIN_ROOT}/hooks/scripts/state-updater.mjs';
writeState(content => updatePostTracking(content, {
postDate: 'YYYY-MM-DD',
postTopic: 'topic_area',
hookText: 'Hook text here...',
charCount: NNNN,
format: 'FORMAT'
}));
"
```
Replace placeholders with actual post data from the published post.
## Step 6: First-Hour Engagement Reminders
After marking as published, show the first-hour battle plan:
```
Post marked as published! Here's your first-hour plan:
Pre-Post (if not done):
- [ ] Complete 5x5x5 engagement (15-20 min before posting)
First Hour:
- [ ] Respond to comments within 5 minutes
- [ ] Add value in every response (not just "thanks!")
- [ ] Ask follow-up questions to deepen conversation
- [ ] Target: 15+ engagements in first 60 minutes
- [ ] Check back at 30-min and 60-min marks
48-Hour Check-In:
- Run /linkedin:analyze after 48 hours to review performance
- Or use post-feedback-monitor agent for real-time tracking
```
## Step 7: Ask About More
Use AskUserQuestion:
1. **Mark another post** — I published more than one
2. **View calendar** — See remaining schedule → `/linkedin:calendar`
3. **Done** — All set for now
## Reference Files
- `${CLAUDE_PLUGIN_ROOT}/assets/drafts/queue.json`
- `${CLAUDE_PLUGIN_ROOT}/references/engagement-frameworks.md`

View file

@ -45,7 +45,7 @@ If posts were added to the queue during this session (`assets/drafts/queue.json`
If a scheduled post was published during this session:
- Verify it was marked as published in queue.json (status = "published")
- If not, remind: "Run /linkedin:publish to update the queue status"
- If not, remind: "Run /linkedin:calendar to mark the post as published and update queue status"
Provide reminders naturally based on what was done in the session. If no LinkedIn content was created, skip the reminders and just ensure state is consistent.

View file

@ -91,8 +91,8 @@ if (followerCount > 0 && followerTarget > 0) {
try {
const todayEntries = queueToday();
const overdueEntries = queueOverdue();
if (todayEntries.length > 0) reminders.push(`You have ${todayEntries.length} post(s) scheduled for today. Run /linkedin:publish after posting to update your tracking.`);
if (overdueEntries.length > 0) reminders.push(`${overdueEntries.length} overdue post(s) in your queue. Run /linkedin:publish to mark as posted, or /linkedin:calendar to reschedule.`);
if (todayEntries.length > 0) reminders.push(`You have ${todayEntries.length} post(s) scheduled for today. Run /linkedin:calendar after posting to mark as published.`);
if (overdueEntries.length > 0) reminders.push(`${overdueEntries.length} overdue post(s) in your queue. Run /linkedin:calendar to mark as posted or reschedule.`);
} catch { /* ignore */ }
// Peak posting time

View file

@ -250,12 +250,12 @@ if (existsSync(STATE_FILE)) {
// Today's scheduled posts
if (queueTodayText) {
context += `## Today's Scheduled Posts\\n${queueTodayText.replace(/\n/g, '\\n')}\\nRun /linkedin:publish after posting to update tracking.\\n\\n`;
context += `## Today's Scheduled Posts\\n${queueTodayText.replace(/\n/g, '\\n')}\\nRun /linkedin:calendar after posting to mark as published.\\n\\n`;
}
// Overdue posts
if (queueOverdueText) {
context += `## OVERDUE Posts\\n${queueOverdueText.replace(/\n/g, '\\n')}\\nRun /linkedin:publish to mark as posted, or /linkedin:calendar to reschedule.\\n\\n`;
context += `## OVERDUE Posts\\n${queueOverdueText.replace(/\n/g, '\\n')}\\nRun /linkedin:calendar to mark as posted or reschedule.\\n\\n`;
}
// Posting reminders
@ -330,10 +330,10 @@ if (existsSync(STATE_FILE)) {
// Queue-related reminders
if (queueTodayCount > 0) {
reminders += `- You have ${queueTodayCount} post(s) scheduled for today. Run /linkedin:publish after posting.\\n`;
reminders += `- You have ${queueTodayCount} post(s) scheduled for today. Run /linkedin:calendar after posting to mark as published.\\n`;
}
if (queueOverdueCount > 0) {
reminders += `- ${queueOverdueCount} overdue post(s) in queue. Run /linkedin:publish or /linkedin:calendar to manage.\\n`;
reminders += `- ${queueOverdueCount} overdue post(s) in queue. Run /linkedin:calendar to mark as posted or reschedule.\\n`;
}
if (reminders) context += `## Posting Reminders\\n${reminders}\\n`;
@ -380,7 +380,7 @@ if (existsSync(STATE_FILE)) {
context += `- Queued posts (next 7 days): ${queueUpcomingCount}\\n`;
if (queueTodayCount > 0) context += `- Today: ${queueTodayCount} post(s)\\n`;
if (queueOverdueCount > 0) context += `- Overdue: ${queueOverdueCount} post(s)\\n`;
context += '- Manage: /linkedin:calendar | Publish: /linkedin:publish\\n\\n';
context += '- Manage + publish: /linkedin:calendar\\n\\n';
}
context += `State file: ${STATE_FILE}\\n`;

View file

@ -43,7 +43,7 @@ let isLinkedin = false;
// Tier 1: Strong signals
const strongSignals = [
'/linkedin:post', '/linkedin:quick', '/linkedin:batch',
'/linkedin:pipeline', '/linkedin:publish', '/linkedin:video',
'/linkedin:pipeline', '/linkedin:calendar', '/linkedin:video',
'/linkedin:multiplatform', '/linkedin:react', '/linkedin:summarize',
'linkedin post', 'lag en post',
'skriv en post', 'write a post', 'quick post', 'create post',

View file

@ -129,8 +129,7 @@ These rules apply to ALL content created by any skill or command:
| `/linkedin:import` | Import CSV export to structured JSON |
| `/linkedin:report` | Generate weekly performance report |
| `/linkedin:batch` | Create a full week of content |
| `/linkedin:calendar` | View and manage post scheduling queue |
| `/linkedin:publish` | Mark scheduled posts as published |
| `/linkedin:calendar` | View + manage post scheduling queue, and run the publish action (mark a scheduled post as published) |
| `/linkedin:pipeline` | Full end-to-end content pipeline |
| `/linkedin:newsletter` | Long-form orchestrator (newsletter editions, essays, series articles) -- single long-form entry point |
| `/linkedin:multiplatform` | Adapt content for other platforms (short-form/cross-format) |