Documentation
¶
Overview ¶
Package project is a newsletter's editorial workspace on disk: research, the concept, the backlog of ideas, issue drafts and what each sent issue taught. lfr check reads it, holds every file to its format and the issues to the concept, compares it with the list on the server, and names the one step that comes next, so an agent can run a newsletter for months with nobody watching, or pick up a topic the owner hands in.
Index ¶
- Constants
- Variables
- func Facts() map[string]any
- func Find(dir string) (string, bool)
- func Init(dir string, c Config, name string) ([]string, error)
- func Rules() []finding.Rule
- type Config
- type Draft
- type Idea
- type Learning
- type Numbers
- type Options
- type Remote
- type RemoteIssue
- type RemoteList
- type Report
Constants ¶
const ( PhaseSetup = "setup" // lfr.json is broken PhaseResearch = "research" // collect voices and peers until the minimums hold PhaseConcept = "concept" // write or repair the editorial brief PhaseLaunch = "launch" // welcome issue, list settings and website match the brief PhaseLearn = "learn" // a sent issue has numbers nobody wrote down PhasePlan = "plan" // the backlog is too thin to pick a good next issue PhaseDraft = "draft" // write the next issue from the backlog PhaseFix = "fix" // a draft breaks a rule PhaseTest = "test" // push the draft and send a test copy PhaseApprove = "approve" // the owner approves the tested draft PhaseSend = "send" // schedule the tested draft for its slot PhaseWait = "wait" // the next issue is scheduled; nothing to do )
Phases, in the order a newsletter moves through them. lfr check names exactly one, with the next step and the skill that covers it.
const ( MinOpenIdeas = 4 // fewer open ideas and the next pick is forced, not chosen SettleTime = 48 * time.Hour // numbers of an issue settle after two days MaxDraftsAhead = 3 // more unsent drafts than this and the backlog rots in issues/ )
Thresholds of the plan and learn phases.
const ( RuleConfig = "project.config" RuleDraft = "project.draft" RuleBacklog = "project.backlog" RuleLearnings = "project.learnings" RuleSync = "project.sync" RuleCadence = "project.cadence" RuleGuardrail = "project.guardrail" RuleSkills = "project.skills" RuleBacklogFormat = "backlog.format" RuleLearningsFormat = "learnings.format" RuleServer = "project.server" RuleImages = "project.images" )
Rule IDs of the project checks.
const ( ConfigFile = "lfr.json" BacklogFile = "backlog.md" LearningsFile = "learnings.md" WelcomeFile = "welcome.md" IssuesDir = "issues" SiteDir = "site" )
Files and folders of a project.
const ( AutonomyDraft = "draft" // the agent drafts and tests; the owner approves each send AutonomySend = "send" // the agent sends when lfr check is clean )
Autonomy settings.
Variables ¶
var Skills = map[string]string{ PhaseSetup: "lfr-setup", PhaseResearch: "lfr-research", PhaseConcept: "lfr-concept", PhaseLaunch: "lfr-launch", PhaseLearn: "lfr-learn", PhasePlan: "lfr-issue", PhaseDraft: "lfr-issue", PhaseFix: "lfr-issue", PhaseTest: "lfr-issue", PhaseApprove: "lfr-issue", PhaseSend: "lfr-issue", PhaseWait: "", }
Skills names the skill for each phase.
var Statuses = []string{"focus", "idea", "planned", "drafted", "sent", "dropped"}
Idea statuses, in the order an idea moves through them.
Functions ¶
func Facts ¶
Facts are the numbers and names skills render with, so a skill never states a minimum or a format the checks do not enforce.
Types ¶
type Config ¶
type Config struct {
SchemaVersion int `json:"schemaVersion"`
List string `json:"list"` // the list's slug on the server
Language string `json:"language"` // en or de
Owner string `json:"owner,omitempty"` // where test copies go
Autonomy string `json:"autonomy,omitempty"` // draft (default) or send
}
Config is lfr.json.
type Draft ¶
type Draft struct {
File string `json:"file"`
Slug string `json:"slug"`
Subject string `json:"subject"`
Preheader string `json:"preheader,omitempty"`
Idea string `json:"idea,omitempty"`
Welcome bool `json:"welcome,omitempty"`
Source string `json:"-"` // the whole file, as the server receives it
Markdown string `json:"-"` // the body after the front matter
}
Draft is an issue on disk: issues/<slug>.md, or welcome.md.
--- subject: Your starter is hungry, not dead preheader: Two feeds a day fix the smell idea: I007 --- Body in Markdown…
func ParseDraft ¶
ParseDraft reads an issue file the way the server will.
func (Draft) Same ¶
func (d Draft) Same(w *RemoteIssue) bool
Same reports whether the server's copy carries this content.
type Idea ¶
type Idea struct {
Line int
ID int
Title string
Pillar string
Added time.Time
Status string
Angle string
Sources []string
}
Idea is one backlog entry.
func ParseBacklog ¶
ParseBacklog reads backlog.md:
### I001 · <title> · <pillar> · added YYYY-MM-DD · <status> <the angle, one or more lines> Sources: V012, E104, https://…, owner
type Numbers ¶
type Numbers struct {
Recipients int `json:"recipients"`
Sent int `json:"sent"`
Bounced int `json:"bounced"`
Clicked int `json:"clicked"`
Replies int `json:"replies"`
Unsubscribed int `json:"unsubscribed"`
Complained int `json:"complained"`
}
Numbers are the counts of a sent issue the learn phase records.
type Options ¶
type Options struct {
Now time.Time
Builtin map[string]bool // citable IDs of lfr's built-in research ("lfr:E012")
Remote *Remote
}
Options steer a check.
type Remote ¶
type Remote struct {
Error string `json:"error,omitempty"` // why the server could not be asked
List *RemoteList `json:"list,omitempty"` // nil: the list does not exist
Issues []RemoteIssue `json:"issues,omitempty"`
}
Remote is what the server knows about the project's list. The CLI fills it; a nil Remote means the files are checked alone.
type RemoteIssue ¶
type RemoteIssue struct {
ID int64 `json:"id"`
Slug string `json:"slug"`
Subject string `json:"subject"`
Preheader string `json:"preheader,omitempty"`
Markdown string `json:"-"`
Status string `json:"status"`
Welcome bool `json:"welcome,omitempty"`
Scheduled time.Time `json:"scheduled,omitzero"`
Started time.Time `json:"started,omitzero"`
Tested time.Time `json:"tested,omitzero"`
Stats *Numbers `json:"stats,omitempty"`
// Findings are the server's checks of the rendered issue: the legal
// footer, size, images. Only for issues not sent yet.
Findings []finding.Finding `json:"findings,omitempty"`
}
RemoteIssue is an issue as the server has it. Markdown is filled for drafts and scheduled issues only.
type RemoteList ¶
type RemoteList struct {
Slug string `json:"slug"`
Name string `json:"name"`
Description string `json:"description"`
Cadence string `json:"cadence"`
Headline string `json:"headline"`
Pitch string `json:"pitch"`
Language string `json:"language"`
Confirmed int `json:"confirmed"`
Paused string `json:"paused,omitempty"`
MinGap time.Duration `json:"min_gap"`
}
RemoteList is the list as the server has it.
type Report ¶
type Report struct {
Dir string `json:"dir"`
List string `json:"list"`
Autonomy string `json:"autonomy"`
Phase string `json:"phase"`
Next string `json:"next"`
Skill string `json:"skill,omitempty"`
Slot time.Time `json:"slot,omitzero"` // when the next issue is due
Server string `json:"server"` // connected, or why not
Research research.Summary `json:"research"`
Ideas int `json:"open_ideas"`
Drafts []Draft `json:"drafts"`
Findings []finding.Finding `json:"findings"`
Concept *concept.Concept `json:"-"`
Backlog []Idea `json:"-"`
Config Config `json:"-"`
}
Report is the result of lfr check.