Skip to content

v1.9.1 ·

The lifecycle tells its own story: a generated release trail

The problem

AIDLC ships roughly every day or two — 15 tags in the first ten days — and none of that history was visible to anyone outside the repo. The GitHub release notes are auto-generated one-liners: v1.7.0 shipped the entire closed feedback loop, and its release body is nothing but a "Full Changelog" link. The website documented what AIDLC is but said nothing about how it got here, so a visitor had no way to judge whether the framework was alive.

Meanwhile the repo was sitting on a rich, structured record of exactly that story. Every completed instance holds an idea.md with a problem statement and the options explored, a requirements.md with numbered acceptance criteria, and a deployment-record.md describing what actually shipped — plus cost and lead-time data. That is a three-act arc — the problem / how it could be solved / how AIDLC solves it — already written down, once per feature, as a byproduct of using the framework. It was dead weight, read by nobody.

The same release carried a second, unrelated fix, and it was about money. Every claim the framework had ever recorded said agent: "unknown". aidlc cost sync resolves a cost provider by that field, finds nothing registered under the name unknown, and skips the session permanently — so usage landed in an unattributed file instead of the instance ledger. Eight instances were affected. aidlc cost reported $0.00 for features that had cost real money to build, and aidlc metrics reported their cost as null.

The agent field had two possible sources and both had failed. Nothing anywhere in the codebase ever set the AIDLC_AGENT environment variable — a repo-wide grep found four read sites and no writer, so the ?? 'unknown' fallback was not a fallback but the only path any real session took. The stash lookup meant to cover for that had disabled itself: it matched candidates by repo identity rather than by session, demanded exactly one match, and the stash had accumulated 21 entries with the same working directory because entries were written when a session started and removed only by a 48-hour timer. From the second concurrent session onward, that path returned null every time. A fallback chain with no working link.

How it could be solved

For the version trail, the first question was whether generation should be automatic at all.

Hand-written posts would read best and were rejected anyway: if writing a post is a chore, the trail goes stale within a few releases. Fully automated generation — a script that emits finished MDX from lifecycle state with no authoring step — was chosen because it makes the trail's own argument literally true. These posts are evidence that the lifecycle produces a durable record, and that claim only holds if generation is genuinely mechanical.

The accepted trade-off is that extraction produces structure, not voice. Problem statements in idea.md are written for a requirements reader: precise, dense, and pasted verbatim they read as internal documentation. Two mitigations were specified rather than deferred. An optional sidecar prose file supersedes any generated section when present, so improving one post never means abandoning generation — the post you are reading uses that escape hatch for both of its first two acts. And the generator fails loudly on thin input: when an act cannot be extracted it reports which files it tried and refuses to emit a stub, because a visibly missing post is better than a post with a blank third act.

Two smaller decisions shaped the rest. Posts are per release, skipping the empty ones, so a patch release that only fixes packaging does not get filler. And the source of truth for which release covers which instance is a committed manifest rather than something derived from git on the fly — hand-maintained, reviewable in one place, and validated as a whole so a new tag fails immediately rather than silently going uncovered.

The cost fix had a narrower option space, because the interesting choice was where to resolve the platform. Reading it lazily at sync time was rejected: by then the session is over and the evidence is gone. Resolving it at claim time, when the process that knows which platform it is running on is still alive, is the only point where the answer is knowable. That left the recovery question — eight instances of usage already stranded in the unattributed file. Backfilling it required care that the first attempt did not take: a reconciliation reported a plausible 1043 segments attributed while having silently written 950 transcript entries into two ledgers twice, because the deduplication was keyed on session while the collision was across sessions. Any attribution figure that becomes money needs an audit for duplicate source records across all destinations before it can be trusted.

How AIDLC solves it

release-blog-trail

A release blog trail on the AIDLC website: one post per release, generated from the lifecycle state that produced it, telling the problem, the options weighed, and what shipped — with what each feature cost to build.

  • Generatorpackages/website/scripts/generate-blog-post.mjs plus seven modules under packages/website/scripts/blog/. --tag, --all, --force, --dry-run, --bootstrap-manifest, --json.
  • Site surfaces/blog/ listing, /blog/<slug>/ post pages, /feed.xml RSS, a nav entry, the latest post on the home page, sitemap routes, and a Blog section in site search.
  • Content — a 17-tag manifest with a JSON Schema, seven act overrides, and two hand-written posts (the-publish-guard, before-the-lifecycle-ran-itself).
  • Release integrationscripts/release.sh generates the post after the clean-tree check and before pnpm build, and commits it with the release.

Six generated posts cover v1.1.0 through v1.6.0. v1.7.0, v1.8.0, v1.8.1, and v1.9.0 are deliberately unpublished, each with a recorded reason.

cost-claim-attribution

A release blog trail on the AIDLC website: one post per release, generated from the lifecycle state that produced it, telling the problem, the options weighed, and what shipped — with what each feature cost to build.

  • Generatorpackages/website/scripts/generate-blog-post.mjs plus seven modules under packages/website/scripts/blog/. --tag, --all, --force, --dry-run, --bootstrap-manifest, --json.
  • Site surfaces/blog/ listing, /blog/<slug>/ post pages, /feed.xml RSS, a nav entry, the latest post on the home page, sitemap routes, and a Blog section in site search.
  • Content — a 17-tag manifest with a JSON Schema, seven act overrides, and two hand-written posts (the-publish-guard, before-the-lifecycle-ran-itself).
  • Release integrationscripts/release.sh generates the post after the clean-tree check and before pnpm build, and commits it with the release.

Six generated posts cover v1.1.0 through v1.6.0. v1.7.0, v1.8.0, v1.8.1, and v1.9.0 are deliberately unpublished, each with a recorded reason.