mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-10-03 13:17:07 +00:00
Add Tech Stack and Monetization sections to OpenPRD (0.3) (#144)
OpenPRD 0.2 fixed eight body sections, none of which asked what the thing is
built on or how it earns. The stack got chosen in the first implementation PR
instead of at review, and a PRD could be filled out completely without anyone
writing down who pays. PRD 0006 had already grown a hand-rolled
`## Business model` section, which is the gap showing.
0.3 adds two required sections between `UX Notes` and `Success Metrics`:
- Tech Stack — languages, frameworks, datastores, third-party services, and
anything the work must not depend on. It makes the requirements costable.
- Monetization — the revenue model: who pays, for what, how much, and when.
`_None._` stays a valid answer, but it now has to be said out loud.
Adding required sections would normally invalidate every document already
written, so a document is now held to the section list its own `openprd:` key
fixes. A 0.2 document keeps conforming with eight sections, forever; a 0.3
document needs ten. Adoption is per document, and `logicsrc prd validate
--expect-version 0.3` (new flag, wiring up the validator option that already
existed) reports the stragglers as OP-L-VERSION.
The front-matter schema is untouched — both additions are body sections.
Conformance bundle proves both directions: invalid/missing-monetization.md
fails with OP-C-SECTION-MISSING, and valid/legacy-0-2.md passes unedited.
This repo's own PRDs 0001-0006 stay at 0.2 as standing evidence that the
compatibility rule holds. PRD 0007 records the decision at 0.3.
Claude-Session: https://claude.ai/code/session_017XRNNm6pK6nPi7rJ6bJNHu
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ca0283caa9
commit
91834179b7
36 changed files with 679 additions and 80 deletions
|
|
@ -1,6 +1,15 @@
|
|||
import { validate as validateSchema } from "@logicsrc/validators";
|
||||
import { slugify } from "./parse.js";
|
||||
import { SECTIONS, type Finding, type PrdCollection, type PrdDocument, type Severity, type ValidationReport } from "./types.js";
|
||||
import {
|
||||
OPENPRD_VERSION,
|
||||
sectionsForVersion,
|
||||
type Finding,
|
||||
type PrdCollection,
|
||||
type PrdDocument,
|
||||
type SectionName,
|
||||
type Severity,
|
||||
type ValidationReport
|
||||
} from "./types.js";
|
||||
|
||||
export interface ValidateOptions {
|
||||
/** Promote lint warnings to errors, for CI that wants a clean collection. */
|
||||
|
|
@ -17,7 +26,7 @@ const TEMPLATE_ID = "0000";
|
|||
* - lives at prd/<id>-<slug>.md with a four-digit <id>
|
||||
* - front-matter validates against openprd-prd.schema.json
|
||||
* - id equals the filename's numeric prefix
|
||||
* - all eight body sections are present in order
|
||||
* - all body sections for the declared version are present in order
|
||||
*
|
||||
* Everything beyond those four is lint: useful, but never the difference
|
||||
* between conforming and not.
|
||||
|
|
@ -73,10 +82,13 @@ export function validatePrdDocument(doc: PrdDocument, options: ValidateOptions =
|
|||
});
|
||||
}
|
||||
|
||||
/* ── 4. The eight sections, present and in order ─────────────────────── */
|
||||
/* ── 4. The standard sections, present and in order ──────────────────── */
|
||||
|
||||
// A document is held to the section list its own `openprd:` version fixes, so
|
||||
// 0.2 documents keep conforming after 0.3 added Tech Stack and Monetization.
|
||||
const expected = [...sectionsForVersion(doc.frontMatter.openprd)];
|
||||
const isStandard = (name: string) => expected.includes(name as SectionName);
|
||||
const present = doc.sections.map((section) => section.name);
|
||||
const expected = [...SECTIONS];
|
||||
|
||||
for (const name of expected) {
|
||||
if (!present.includes(name)) {
|
||||
|
|
@ -84,12 +96,12 @@ export function validatePrdDocument(doc: PrdDocument, options: ValidateOptions =
|
|||
code: "OP-C-SECTION-MISSING",
|
||||
severity: "error",
|
||||
message: `missing required section "## ${name}"`,
|
||||
hint: `The eight sections are: ${expected.join(", ")}`
|
||||
hint: `OpenPRD ${doc.frontMatter.openprd ?? OPENPRD_VERSION} requires: ${expected.join(", ")}`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const required = present.filter((name) => expected.includes(name as (typeof SECTIONS)[number]));
|
||||
const required = present.filter(isStandard);
|
||||
const ordered = expected.filter((name) => required.includes(name));
|
||||
if (required.length === ordered.length && required.join("|") !== ordered.join("|")) {
|
||||
add({
|
||||
|
|
@ -99,13 +111,13 @@ export function validatePrdDocument(doc: PrdDocument, options: ValidateOptions =
|
|||
});
|
||||
}
|
||||
|
||||
const extra = present.filter((name) => !expected.includes(name as (typeof SECTIONS)[number]));
|
||||
const extra = present.filter((name) => !isStandard(name));
|
||||
for (const name of extra) {
|
||||
add({
|
||||
code: "OP-L-EXTRA-SECTION",
|
||||
severity: "info",
|
||||
line: doc.sections.find((s) => s.name === name)?.line,
|
||||
message: `"## ${name}" is not one of the eight standard sections`,
|
||||
message: `"## ${name}" is not one of the ${expected.length} standard sections`,
|
||||
hint: "Use a ### subsection inside a standard section instead"
|
||||
});
|
||||
}
|
||||
|
|
@ -122,7 +134,7 @@ export function validatePrdDocument(doc: PrdDocument, options: ValidateOptions =
|
|||
}
|
||||
|
||||
for (const section of doc.sections) {
|
||||
if (!expected.includes(section.name as (typeof SECTIONS)[number])) continue;
|
||||
if (!isStandard(section.name)) continue;
|
||||
if (!section.empty) continue;
|
||||
add({
|
||||
code: "OP-L-EMPTY-SECTION",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue