Netspective Logo
Spec-Driven Development with spec-kit

The Plan and the Task List

Where the coding agent acts as architect. It proposes a technical approach, defends it in writing, and the numbered build steps fall out of it.

The specification is agreed and the ambiguities are settled. This is where technology finally enters — proposed and defended by the coding agent, then broken into numbered build steps and checked for gaps.

2.5 /speckit.plan — the technical approach

Now, and only now, technology is named — and it is named by the coding agent rather than by us.

What we would type:

/speckit.plan

Recommend and justify a technical approach for this specification.
Constraints: it must run unattended on a schedule; it must file drafts
on an existing self-hosted WordPress site; it must never publish; and
whatever you build must be reviewable and kept in version control.
Do not assume a platform. Record what you rejected, and why.

Note what that prompt does not contain: a design. It states constraints and asks for a recommendation. This is the phase where the coding agent acts as the architect, and the value of it comes from the agent having to defend a choice in writing rather than transcribe one of ours. If we hand it a finished design here, every later artifact inherits a decision nobody ever justified.

Comes back: five files. The interesting parts of each:

plan.md — Technical Context and the Constitution Check.

## Technical Context

Approach          a scheduled service, written in Python
Entry point       one command, started by the host's scheduler
Key libraries     feed parsing, HTTP, the model SDK, a WordPress
                  REST client
Storage           a small local store for the seen-item history
Target platform   the client's existing Linux host
Constraints       must not publish; must attribute; explicit config
Scale             ~6 sources, 1 run/day, 1 article/run

## Constitution Check

I.   Human approval        PASS — the publishing call is fixed to the
                           draft state; no code path sets another.
II.  Attribution           PASS — the article contract requires a
                           non-empty sources array.
III. Explicit config       PASS — every setting is read from one file,
                           with no silent defaults.
IV.  Validate first        PASS — tests and one supervised run precede
                           any scheduled execution.
V.   Least privilege       PASS — dedicated WordPress account, Author
                           role, application password.

No violations. Complexity Tracking table not required.

research.md — the decisions, and what was rejected. The first of them is the one that used to be ours to make:

## Decision: A scheduled service, written in Python
Rationale: the build artifact is code — reviewable, testable, diffable
and under version control. It cannot be edited out from under the
specification by somebody opening a visual editor, which is what makes
the day-two argument in 2.10 hold at all.
Alternatives considered: a hosted workflow platform (rejected — the
artifact becomes a canvas that drifts silently from the spec, and the
drift stays invisible until somebody asks why); a managed no-code
service (rejected — a recurring cost, credentials held by a third
party, and a weaker audit trail than a repository).
Noted: this is a recommendation, not a requirement. Overruling it is
one amendment to the plan, and the task list is regenerated from it.

## Decision: Read RSS feeds in preference to fetching pages
Rationale: a feed is a published, stable interface. Fetching and parsing
a page is more work to build, more fragile in operation, and more likely
to draw an objection from the source.
Alternatives considered: fetch every source as a page (rejected —
needless fragility where a feed exists); a third-party aggregation API
(rejected — an external dependency and a recurring cost for something a
well-established library already does).

## Decision: De-duplicate on canonical URL
Rationale: stable across re-publication; present for every item from
both feeds and pages.
Alternatives considered: title (rejected — edited after publication);
feed GUID (rejected — not universal, and absent from fetched pages);
content hash (rejected — a one-character edit defeats it).

## Decision: WordPress via application password on a dedicated account
Rationale: revocable independently of a human user, scoped to one
integration, and accepted directly by the WordPress REST API without
registering an OAuth application.
Alternatives considered: OAuth2 (rejected — more setup for no gain here);
an existing editor's account (rejected — violates Principle V, and
attributes machine-written drafts to a person).
Risk noted: some WordPress security plugins disable application
passwords. To be confirmed against the client's installation.

## Decision: One synthesised article per run
Rationale: per the clarification session. Editorial value lies in
connecting stories rather than restating each.
Alternatives considered: one article per item (rejected — produces
review volume the team cannot absorb, defeating SC-003).

data-model.md formalises the four entities from the specification, with the seen-item record keyed on canonical URL and a stated retention window.

contracts/article.schema.json is the one to look at closely. The writing step returns a structured result rather than prose, and that structure is a genuine contract — the thing the writer must produce and the thing the filing step consumes.

{
  "headline":  { "type": "string",  "maxLength": 120 },
  "body":      { "type": "string",  "minLength": 1200 },
  "tags":      { "type": "array",   "items": { "type": "string" },
                 "minItems": 1, "maxItems": 6 },
  "sources":   { "type": "array",   "items": { "type": "string" },
                 "minItems": 1 }
}

"sources" having minItems: 1 is Principle II expressed as something a machine can enforce. An article the writer cannot attribute fails the parser rather than reaching an editor.

quickstart.md describes the manual validation run: trigger once by hand, confirm items are collected from each source, read the produced draft in WordPress, confirm it is not publicly visible.

A distinction the plan makes explicit. Everything above exists only while the pipeline is being built. None of it runs.

What exists only while building, and what actually runs every day

FIGURE 8 — Built once, on the left. Runs every morning, on the right. This is the question clients ask first: does this need an AI engineer on hand every day? It does not.

2.6 /speckit.tasks — the concrete build steps

Comes back: tasks.md, ordered so that each user story can be completed and demonstrated on its own. Abridged:

## Phase 1: Setup
- [ ] T001 Initialise the repository and dependency manifest
- [ ] T002 Create the WordPress service account, Author role
- [ ] T003 Generate an application password for that account
- [ ] T004 Add config.toml with every setting stated, none defaulted

## Phase 2: Foundational (blocking)
- [ ] T005 src/schedule.py — one entry point, daily 06:00,
           timezone stated explicitly
- [ ] T006 config.toml — the source list, and which publish a feed

## Phase 3: User Story 1 — Watch the sources (P1)   MVP
- [ ] T009 [P] [US1] src/collect/feed.py — read the feed for each
           source that publishes one
- [ ] T010 [P] [US1] src/collect/page.py — fetch and extract for
           sources without a feed
- [ ] T012 [US1] src/collect/__init__.py — combine both collectors
- [ ] T013 [US1] src/dedupe.py — discard by canonical URL,
           history window 30 days
- [ ] T014 [US1] Handle an unreachable source without failing the run
**Checkpoint**: a run collects new items and skips seen ones.

## Phase 4: User Story 2 — Draft the article (P2)
- [ ] T016 [US2] src/write/client.py — the model call
- [ ] T017 [US2] src/write/parse.py — validate the response against
           contracts/article.schema.json; reject on failure
- [ ] T018 [US2] prompts/writer.md — house style, target length,
           attribution rule
- [ ] T019 [US2] Handle a malformed or empty response
**Checkpoint**: a run produces a structured article with sources.

## Phase 5: User Story 3 — File it (P3)
- [ ] T021 [US3] src/publish/wordpress.py — create the post with
           status draft, mapped from the article contract
- [ ] T022 [US3] src/notify.py — notify a person when a run fails

## Phase 6: Polish & Cross-Cutting
- [ ] T023 Run the tests and the type check; fix every reported error
- [ ] T024 Deploy to the client host, SCHEDULE NOT ENABLED

Read T009 and T013 again. Each names a real file and the decision that matters. A task that said "set up de-duplication" would have passed a casual review and left the actual decision — de-duplicate on what — to be made silently by whoever built it. This is what "explicit" means in Principle III.

Note also what T013 is not. It is not "add a de-duplication component". The method does not care whether the thing that does the work is a library call, a class or twelve lines in a file. It cares that the requirement is named, the key is stated, and the file it lives in is written down before anyone opens an editor.

T024 says SCHEDULE NOT ENABLED, and it is the last task. The build finishes with something that exists and does not run.

2.7 /speckit.analyze — checking the artifacts still agree

Read-only. Six passes. On this project it would plausibly report something like:

## Specification Analysis Report

| ID | Category         | Severity | Location        | Summary
|----|------------------|----------|-----------------|------------------
| A1 | Coverage gap     | HIGH     | FR-009 / tasks  | FR-009 requires
|    |                  |          |                 | notification when a
|    |                  |          |                 | RUN fails; T022 only
|    |                  |          |                 | covers the filing
|    |                  |          |                 | step failing
| A2 | Underspecified   | MEDIUM   | spec SC-003     | "less time than
|    |                  |          |                 | from scratch" has
|    |                  |          |                 | no baseline
| A3 | Inconsistency    | LOW      | plan / research | plan says ~6
|    |                  |          |                 | sources; source
|    |                  |          |                 | list is unfixed

## Coverage Summary
Requirements: 9   Tasks: 24   Requirements with >= 1 task: 8 (89%)
Constitution alignment issues: 0   Critical: 0

A1 is the kind of finding that justifies the command on its own. FR-009 says a person is notified when a run fails; the tasks only notify when the filing step fails. A source going unreachable at 06:00 would produce a silent no-op. Caught here, it is one more task. Caught in production, it is a fortnight of nobody noticing the pipeline stopped.

Nothing is modified. You decide what to do with each finding.

One requirement traced from user story to the file that satisfies it

FIGURE 9 — Why does that file exist? Follow the chain back. Five links, and every one of them is a line in a file somebody can open.

How is this guide?

Last updated on

On this page