@popoverai/dotrequirements 0.34.0 → 0.34.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -251,13 +251,13 @@ Browser testing needs a Stagehand model and the API key for that model's provide
251
251
  {
252
252
  "defaultURL": "https://your-app.com",
253
253
  "browserTest": {
254
- "modelName": "gateway/anthropic/claude-haiku-4-5",
254
+ "modelName": "gateway/anthropic/claude-haiku-5.5",
255
255
  "modelApiKey": "your-api-key"
256
256
  }
257
257
  }
258
258
  ```
259
259
 
260
- Pick any Stagehand-supported model. Example values: `gateway/anthropic/claude-haiku-4-5` (Vercel AI Gateway key), `google/gemini-3-flash-preview` (Gemini key).
260
+ Pick any Stagehand-supported model. Example values: `gateway/anthropic/claude-haiku-5.5` (Vercel AI Gateway key), `google/gemini-3-flash-preview` (Gemini key).
261
261
 
262
262
  Optional settings: `vercelBypassSecret`, `browserbaseApiKey`, `browserbaseProjectId`. Setting both Browserbase fields switches runs from LOCAL (spawns Playwright on your machine) to BROWSERBASE (managed cloud browsers).
263
263
 
@@ -52,7 +52,7 @@ export async function acceptanceTestCommand(requirementKey, url, options) {
52
52
  console.error("Error: no browser-test model configured in project-settings.json.");
53
53
  console.log("Add a block like:\n" +
54
54
  ' "browserTest": {\n' +
55
- ' "modelName": "gateway/anthropic/claude-haiku-4-5",\n' +
55
+ ' "modelName": "gateway/anthropic/claude-haiku-5.5",\n' +
56
56
  ' "modelApiKey": "..."\n' +
57
57
  " }\n" +
58
58
  "to .requirements/project-settings.json. See the docs for supported models.");
@@ -73,8 +73,9 @@ outcome at .requirements/solutions/<solution>/<outcome>.md. With a repository
73
73
  connected, a file kept on the pull request is committed and submitted as
74
74
  file:<path>, unsynced, since it reaches the project's requirements when the
75
75
  pull request merges; one kept in the app is synced alone with
76
- \`dotreq sync --repo-contributes <path>\`, which writes the document id into its
77
- frontmatter, and submitted as document:<id>. With none, submit file:<path>: it
76
+ \`dotreq sync <path> --repo-wins\`, which writes the document id into its
77
+ frontmatter and lets a revision replace the version before it, and submitted
78
+ as document:<id>. With none, submit file:<path>: it
78
79
  is saved on the solution's branch, and the document id is written into its
79
80
  frontmatter.
80
81
 
@@ -1,11 +1,12 @@
1
1
  /**
2
2
  * Which solution and agent a verb is for (VERB-4). Inside a runner the run
3
3
  * itself says — the runner writes a run file and names it in
4
- * `DOTREQ_RUN_FILE`: `{ solutionId, agent, branch }` — so an agent types no
5
- * ids. Outside one, the agent names them with `--solution` and `--agent`,
6
- * which win wherever they are given. The outcome is the backend's to work
7
- * out — the one the agent is at — unless `--outcome` names it, as an agent
8
- * at more than one does.
4
+ * `DOTREQ_RUN_FILE`: `{ solutionId, agent, branch, outcome }` — so an agent
5
+ * types no ids. Outside one, the agent names them with `--solution` and
6
+ * `--agent`, which win wherever they are given. The outcome is the one the
7
+ * run was handed, so a verb records on the outcome the agent is at even
8
+ * when it drafts one outcome and reviews another (VERB-3.4, 6.0);
9
+ * `--outcome` names another. Outside a run, the backend works it out.
9
10
  */
10
11
  /** The options every solution verb an agent runs carries. */
11
12
  export type WhereOptions = {
@@ -19,7 +20,7 @@ export type Where = {
19
20
  solutionId: string;
20
21
  /** The agent working: a plan's named agent, or one dedicated to an outcome ("Pitch/drafter"). */
21
22
  agent: string;
22
- /** The outcome, when the agent named it; otherwise the one the agent is at. */
23
+ /** The outcome the agent named, or the one its run was handed; otherwise the backend's to work out. */
23
24
  outcome?: string;
24
25
  /**
25
26
  * The solution's branch, which the runner has checked out (SOLBRANCH-1):
@@ -17,11 +17,12 @@ function fromRun() {
17
17
  }
18
18
  if (!parsed || typeof parsed !== "object")
19
19
  return {};
20
- const { solutionId, agent, branch } = parsed;
20
+ const { solutionId, agent, branch, outcome } = parsed;
21
21
  return {
22
22
  solutionId: typeof solutionId === "string" ? solutionId : undefined,
23
23
  agent: typeof agent === "string" && agent ? agent : undefined,
24
24
  branch: typeof branch === "string" && branch ? branch : undefined,
25
+ outcome: typeof outcome === "string" && outcome ? outcome : undefined,
25
26
  };
26
27
  }
27
28
  /** The outcome named, by `--outcome` or its old name `--step`; blank is none. */
@@ -31,10 +32,15 @@ function outcomeNamed(options) {
31
32
  /** The run's own, with anything the agent named winning over it. */
32
33
  export function whereOf(options) {
33
34
  const run = fromRun();
35
+ const agent = options.agent?.trim() || run.agent;
36
+ // The run's outcome is its own agent's on its own solution: naming
37
+ // another is working outside the run.
38
+ const inTheRun = (options.solution === undefined || options.solution === run.solutionId) &&
39
+ agent === run.agent;
34
40
  return {
35
41
  solutionId: options.solution ?? run.solutionId,
36
- agent: options.agent?.trim() || run.agent,
37
- outcome: outcomeNamed(options),
42
+ agent,
43
+ outcome: outcomeNamed(options) ?? (inTheRun ? run.outcome : undefined),
38
44
  // The run's branch is the run's solution's: naming another solution
39
45
  // is working outside the run.
40
46
  branch: options.solution === undefined || options.solution === run.solutionId
@@ -1,8 +1,11 @@
1
1
  /**
2
2
  * `dotreq solution start <solution-id> <agent>`: begin an agent's work by
3
- * hand. A stalled agent is started again, and it takes the part of an
4
- * outcome's work it has due: the one `--outcome` names, or its first in the
5
- * plan's order.
3
+ * hand. A stalled agent is started again. With the project's agent
4
+ * connected, it is sent for and its runner takes the first part it has
5
+ * due, in the plan's order, as after Start again in the app; `--outcome`
6
+ * naming any other is refused. With none connected, it takes the part of
7
+ * an outcome's work it has due itself: the one `--outcome` names, or its
8
+ * first in the plan's order (STEP-REVIEW-24.10, 24.11).
6
9
  */
7
10
  export declare function startCommand(solutionId: string, agent: string, options: {
8
11
  outcome?: string;
@@ -3,18 +3,29 @@ import { solutionClient } from "./client.js";
3
3
  import { onOutcome } from "./run-file.js";
4
4
  /**
5
5
  * `dotreq solution start <solution-id> <agent>`: begin an agent's work by
6
- * hand. A stalled agent is started again, and it takes the part of an
7
- * outcome's work it has due: the one `--outcome` names, or its first in the
8
- * plan's order.
6
+ * hand. A stalled agent is started again. With the project's agent
7
+ * connected, it is sent for and its runner takes the first part it has
8
+ * due, in the plan's order, as after Start again in the app; `--outcome`
9
+ * naming any other is refused. With none connected, it takes the part of
10
+ * an outcome's work it has due itself: the one `--outcome` names, or its
11
+ * first in the plan's order (STEP-REVIEW-24.10, 24.11).
9
12
  */
10
13
  export async function startCommand(solutionId, agent, options) {
11
14
  const outcome = (options.outcome ?? options.step)?.trim() || undefined;
12
15
  const client = solutionClient();
13
- await client.mutation(api.solutions.mutations.start, {
16
+ const started = await client.mutation(api.solutions.mutations.start, {
14
17
  solutionId,
15
18
  agent,
16
19
  ...(outcome ? { outcome } : {}),
17
20
  });
18
- console.log(`Started ${agent}${onOutcome(outcome)}.`);
21
+ if (!started) {
22
+ // A backend from before it said what it did.
23
+ console.log(`Started ${agent}${onOutcome(outcome)}.`);
24
+ return;
25
+ }
26
+ const work = `${started.part === "review" ? "review" : "draft"} "${started.outcome}"`;
27
+ console.log(started.sentFor
28
+ ? `Sent for ${agent}: its runner will ${work}.`
29
+ : `Started ${agent}: it can ${work}.`);
19
30
  }
20
31
  //# sourceMappingURL=start.js.map
@@ -7,6 +7,7 @@ export { buildRequirementMarkdown, buildRequirementsMarkdown, DEFAULT_DELIMITER,
7
7
  export type { ConvexRequirement } from "./conversions.js";
8
8
  export { buildMetadata, constructKey, convexToRequirements, extractRequirementKeys, groupByRoot, parseKey, requirementsToConvex, } from "./conversions.js";
9
9
  export { DELIMITER_PATTERN, type ExtractedRequirementBlock, extractRequirementBlocks, findRequirementById, flattenRequirementTree, getAllRequirements, parseCriterionLine, parseRequirementBlock, parseRequirementBlocksFromMarkdown, parseRootLine, ROOT_LABEL_MARKER, splitRequirementFenceContent, stripFrontmatterBlock, } from "./parser-core.js";
10
+ export { carriesBranchVersion } from "./provenance.js";
10
11
  export type { Metadata, ParsedCriterion, RequirementKey, RequirementNode, RequirementPrefix, RequirementsFile, } from "./schemas.js";
11
12
  export { buildRequirementKey, canonicalRequirementKey, MetadataSchema, normalizePrefix, ParsedCriterionSchema, parseRequirementKey, REQUIREMENT_KEY_PATTERN, REQUIREMENT_PREFIX_PATTERN, RequirementKeySchema, RequirementNodeSchema, RequirementPrefixSchema, RequirementsFileSchema, ValidationError, validateKey, validateMetadata, validatePrefix, validateRequirementNode, validateRequirementsFile, } from "./schemas.js";
12
13
  export { composeMarkdownWithTitle, effectiveTitle, type SplitMarkdown, splitLeadingH1, } from "./title-markdown.js";
@@ -14,6 +14,9 @@ export { DELIMITER_PATTERN, extractRequirementBlocks, findRequirementById, flatt
14
14
  ROOT_LABEL_MARKER, splitRequirementFenceContent,
15
15
  // A requirements file read from a repository in the browser (SOLDOC-12).
16
16
  stripFrontmatterBlock, } from "./parser-core.js";
17
+ // Whether a publish that says it lands a branch version matches it
18
+ // (BRANCH-7.11), checked by the web app's merge and by the backend alike
19
+ export { carriesBranchVersion } from "./provenance.js";
17
20
  // Schemas and types (uses zod - browser-safe)
18
21
  export { buildRequirementKey, canonicalRequirementKey,
19
22
  // Zod schemas
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Whether a publish that says it lands a branch version matches that version
3
+ * (BRANCH-7.11): its title, its default prefix, and each of its requirements,
4
+ * key and wording, in the same tree and order. Pure and browser-safe, so the web
5
+ * app's merge checks every document before landing any, and the backend
6
+ * checks each publish, by the same rule.
7
+ *
8
+ * The merge publishes each version as the app's editor stores it, so the
9
+ * comparison is of what the editor keeps, not of the text:
10
+ * - a criterion's position is its place in the tree, since the editor
11
+ * numbers criteria from 0 in order, and a label is compared without case,
12
+ * since it writes a Gherkin keyword as `Given` however it was written;
13
+ * - the title is the leading heading however it is written, `#` or
14
+ * underlined, falling back to the version's own title as the merge does;
15
+ * - the writing between requirements isn't compared at all (7.11.0): the
16
+ * editor lays lists and tables out its own way and drops what it doesn't
17
+ * keep, such as an HTML comment (BRANCH-2.2).
18
+ */
19
+ export declare function carriesBranchVersion(published: {
20
+ markdownContent: string;
21
+ defaultPrefix?: string;
22
+ }, version: {
23
+ markdownContent: string;
24
+ title?: string;
25
+ defaultPrefix?: string;
26
+ }): boolean;
27
+ //# sourceMappingURL=provenance.d.ts.map
@@ -0,0 +1,66 @@
1
+ import { parseRequirementBlocksFromMarkdown } from "./parser-core.js";
2
+ import { splitLeadingH1 } from "./title-markdown.js";
3
+ /**
4
+ * Whether a publish that says it lands a branch version matches that version
5
+ * (BRANCH-7.11): its title, its default prefix, and each of its requirements,
6
+ * key and wording, in the same tree and order. Pure and browser-safe, so the web
7
+ * app's merge checks every document before landing any, and the backend
8
+ * checks each publish, by the same rule.
9
+ *
10
+ * The merge publishes each version as the app's editor stores it, so the
11
+ * comparison is of what the editor keeps, not of the text:
12
+ * - a criterion's position is its place in the tree, since the editor
13
+ * numbers criteria from 0 in order, and a label is compared without case,
14
+ * since it writes a Gherkin keyword as `Given` however it was written;
15
+ * - the title is the leading heading however it is written, `#` or
16
+ * underlined, falling back to the version's own title as the merge does;
17
+ * - the writing between requirements isn't compared at all (7.11.0): the
18
+ * editor lays lists and tables out its own way and drops what it doesn't
19
+ * keep, such as an HTML comment (BRANCH-2.2).
20
+ */
21
+ export function carriesBranchVersion(published, version) {
22
+ const theirs = requirementsOf(published.markdownContent);
23
+ const ours = requirementsOf(version.markdownContent);
24
+ return (theirs !== null &&
25
+ ours !== null &&
26
+ theirs.length === ours.length &&
27
+ theirs.every((each, i) => each === ours[i]) &&
28
+ (titleOf(published.markdownContent) ?? "") ===
29
+ (titleOf(version.markdownContent) ?? version.title ?? "") &&
30
+ (published.defaultPrefix ?? "") === (version.defaultPrefix ?? ""));
31
+ }
32
+ /** Each requirement as the editor keeps it, in order; null when a block doesn't parse. */
33
+ function requirementsOf(markdown) {
34
+ let trees;
35
+ try {
36
+ trees = parseRequirementBlocksFromMarkdown(markdown);
37
+ }
38
+ catch {
39
+ return null;
40
+ }
41
+ const out = [];
42
+ const walk = (node, path) => {
43
+ out.push([path, node.label.trim().toLowerCase(), squash(node.content)].join("\u0000"));
44
+ node.children.forEach((child, index) => {
45
+ walk(child, `${path}.${index}`);
46
+ });
47
+ };
48
+ for (const tree of trees)
49
+ walk(tree, tree.id.split(".")[0]);
50
+ return out;
51
+ }
52
+ function squash(text) {
53
+ return text.replace(/\s+/g, " ").trim();
54
+ }
55
+ /** The leading heading's text, `#` or underlined with `=`; undefined when there is none. */
56
+ function titleOf(markdown) {
57
+ const atx = splitLeadingH1(markdown).title;
58
+ if (atx != null)
59
+ return squash(atx);
60
+ const lines = markdown.replace(/^\s*\n/, "").split("\n");
61
+ if (lines.length >= 2 && lines[0].trim() && /^=+\s*$/.test(lines[1])) {
62
+ return squash(lines[0]);
63
+ }
64
+ return undefined;
65
+ }
66
+ //# sourceMappingURL=provenance.js.map
@@ -435,7 +435,8 @@ function localWrite(doc, cloudById, usedPaths, requirementsDir, outcome, mode) {
435
435
  /**
436
436
  * SOLDOC-6: where a pulled document lands. A file it is already linked to
437
437
  * keeps its path (SYNC-DISCOVERY-2.0); a document that belongs to a solution
438
- * lands in that solution's directory, named for its outcome; a document
438
+ * lands in that solution's directory, named for its outcome (SOLDOC-19 when
439
+ * the outcome holds more than one); a document
439
440
  * that is not a requirements document and belongs to no solution has no
440
441
  * place, and the pull says so rather than guessing.
441
442
  */
@@ -445,13 +446,11 @@ function placeCloudDocument(cloudDoc, existingPath, usedPaths, requirementsDir)
445
446
  const isRequirementsDoc = cloudDoc.isRequirementsDoc !== false;
446
447
  if (cloudDoc.solution) {
447
448
  // SOLDOC-9: the solution's own directory name; a backend from before it
448
- // was sent leaves it out, and the title's slug stands in.
449
- const candidate = solutionDocumentPath(requirementsDir, cloudDoc.solution.directory ??
450
- sanitizeFileName(cloudDoc.solution.title ?? cloudDoc.solution.solutionId), cloudDoc.solution.outcome, isRequirementsDoc);
451
- if (usedPaths.has(candidate) || fs.existsSync(candidate)) {
452
- throw new Error(`"${cloudDoc.title}" belongs at ${path.relative(path.dirname(requirementsDir), candidate)}, but another file is already there.`);
453
- }
454
- return candidate;
449
+ // was sent leaves it out, and the title's slug stands in. SOLDOC-19: the
450
+ // file name the backend gives it, or the next free one when another file
451
+ // is there.
452
+ return solutionDocumentPath(requirementsDir, cloudDoc.solution.directory ??
453
+ sanitizeFileName(cloudDoc.solution.title ?? cloudDoc.solution.solutionId), cloudDoc.solution.outcome, isRequirementsDoc, cloudDoc.solution.fileName, (candidate) => usedPaths.has(candidate) || fs.existsSync(candidate));
455
454
  }
456
455
  if (!isRequirementsDoc) {
457
456
  throw new Error(`"${cloudDoc.title}" is not a requirements document and belongs to no solution, so the pull has nowhere to put it.`);
@@ -55,10 +55,20 @@ export declare function resolveBelonging(filePath: string, frontmatter: {
55
55
  outcome?: string;
56
56
  }, claim: PathClaim | undefined, solutions: SolutionForSync[] | undefined): Resolved;
57
57
  /**
58
- * SOLDOC-6, SOLDOC-9: where a pulled document that belongs to a solution
59
- * lands when it has no file yet: in the solution's own directory, named for
60
- * its outcome, with the `.requirements.md` name when it is a requirements
61
- * document.
58
+ * SOLDOC-4, SOLDOC-19: the outcome a file's stem names, of those given: the
59
+ * one whose slug the stem is, or else the one whose slug it is followed by a
60
+ * suffix from -2 up, as an outcome's later documents are named. So `spec-2`
61
+ * is Spec 2's when there is a Spec 2, and the Spec's otherwise.
62
62
  */
63
- export declare function solutionDocumentPath(requirementsDir: string, solutionDirectory: string, outcome: string, isRequirementsDoc: boolean): string;
63
+ export declare function outcomeNamedBy(stem: string, outcomes: readonly string[]): string | undefined;
64
+ /**
65
+ * SOLDOC-6, SOLDOC-9, SOLDOC-19: where a pulled document that belongs to a
66
+ * solution lands when it has no file yet: in the solution's own directory,
67
+ * at the file name the backend gives it, which counts the outcome's other
68
+ * documents. A backend from before it was sent leaves it out, and the
69
+ * outcome's plain name stands in: its slug, with the `.requirements.md` name
70
+ * when it is a requirements document. When that file is taken, the document
71
+ * takes the outcome's next free name from -2 up.
72
+ */
73
+ export declare function solutionDocumentPath(requirementsDir: string, solutionDirectory: string, outcome: string, isRequirementsDoc: boolean, fileName?: string | null, taken?: (candidate: string) => boolean): string;
64
74
  //# sourceMappingURL=solution-documents.d.ts.map
@@ -60,7 +60,12 @@ export function resolveBelonging(filePath, frontmatter, claim, solutions) {
60
60
  warning: `${name}: its path names a different solution than its frontmatter ("${owner.title}"); the frontmatter's belonging is kept.`,
61
61
  };
62
62
  }
63
- if (sanitizeFileName(declared.outcome) !== claim.outcomeSlug) {
63
+ // Read against the solution's other outcomes too: `spec-2` names Spec 2,
64
+ // not the Spec, when the plan has both.
65
+ if (outcomeNamedBy(claim.outcomeSlug, [
66
+ ...(owner?.outcomes ?? []),
67
+ declared.outcome,
68
+ ]) !== declared.outcome) {
64
69
  return {
65
70
  belonging: declared,
66
71
  warning: `${name}: its filename names a different outcome than its frontmatter ("${declared.outcome}"); the frontmatter's belonging is kept.`,
@@ -78,20 +83,53 @@ export function resolveBelonging(filePath, frontmatter, claim, solutions) {
78
83
  throw new Error(`${name}: its directory "${claim.solutionSlug}" matches more than one solution (${matches.map((s) => `"${s.title}"`).join(", ")}), so it can't be placed.`);
79
84
  }
80
85
  const [solution] = matches;
81
- const outcome = solution.outcomes.find((o) => sanitizeFileName(o) === claim.outcomeSlug);
86
+ const outcome = outcomeNamedBy(claim.outcomeSlug, solution.outcomes);
82
87
  if (!outcome) {
83
88
  throw new Error(`${name}: "${claim.outcomeSlug}" matches no outcome of "${solution.title}". Its plan produces: ${solution.outcomes.map((o) => `${o} (${sanitizeFileName(o)}.md)`).join(", ")}.`);
84
89
  }
85
90
  return { belonging: { solutionId: solution.solutionId, outcome } };
86
91
  }
87
92
  /**
88
- * SOLDOC-6, SOLDOC-9: where a pulled document that belongs to a solution
89
- * lands when it has no file yet: in the solution's own directory, named for
90
- * its outcome, with the `.requirements.md` name when it is a requirements
91
- * document.
93
+ * SOLDOC-4, SOLDOC-19: the outcome a file's stem names, of those given: the
94
+ * one whose slug the stem is, or else the one whose slug it is followed by a
95
+ * suffix from -2 up, as an outcome's later documents are named. So `spec-2`
96
+ * is Spec 2's when there is a Spec 2, and the Spec's otherwise.
92
97
  */
93
- export function solutionDocumentPath(requirementsDir, solutionDirectory, outcome, isRequirementsDoc) {
98
+ export function outcomeNamedBy(stem, outcomes) {
99
+ const plain = outcomes.find((o) => sanitizeFileName(o) === stem);
100
+ if (plain !== undefined)
101
+ return plain;
102
+ const later = /^(.+)-([1-9]\d*)$/.exec(stem);
103
+ if (!later || Number(later[2]) < 2)
104
+ return undefined;
105
+ return outcomes.find((o) => sanitizeFileName(o) === later[1]);
106
+ }
107
+ /**
108
+ * SOLDOC-6, SOLDOC-9, SOLDOC-19: where a pulled document that belongs to a
109
+ * solution lands when it has no file yet: in the solution's own directory,
110
+ * at the file name the backend gives it, which counts the outcome's other
111
+ * documents. A backend from before it was sent leaves it out, and the
112
+ * outcome's plain name stands in: its slug, with the `.requirements.md` name
113
+ * when it is a requirements document. When that file is taken, the document
114
+ * takes the outcome's next free name from -2 up.
115
+ */
116
+ export function solutionDocumentPath(requirementsDir, solutionDirectory, outcome, isRequirementsDoc, fileName, taken = () => false) {
94
117
  const suffix = isRequirementsDoc ? ".requirements.md" : ".md";
95
- return path.join(requirementsDir, SOLUTIONS_DIR, solutionDirectory || "solution", `${sanitizeFileName(outcome) || "outcome"}${suffix}`);
118
+ const directory = path.join(requirementsDir, SOLUTIONS_DIR, solutionDirectory || "solution");
119
+ const slug = sanitizeFileName(outcome) || "outcome";
120
+ // Only a plain file name of the document's kind, never a path.
121
+ const given = fileName &&
122
+ fileName === path.basename(fileName) &&
123
+ !fileName.startsWith(".") &&
124
+ stemOf(fileName) !== "" &&
125
+ fileName.endsWith(suffix) &&
126
+ (isRequirementsDoc || !fileName.endsWith(".requirements.md"))
127
+ ? fileName
128
+ : `${slug}${suffix}`;
129
+ let candidate = path.join(directory, given);
130
+ for (let n = 2; taken(candidate); n++) {
131
+ candidate = path.join(directory, `${slug}-${n}${suffix}`);
132
+ }
133
+ return candidate;
96
134
  }
97
135
  //# sourceMappingURL=solution-documents.js.map
@@ -56,6 +56,8 @@ export interface CloudDocument {
56
56
  title: string | null;
57
57
  /** SOLDOC-9: the solution's directory name; absent from an older backend. */
58
58
  directory?: string | null;
59
+ /** SOLDOC-19: the document's file name in that directory; absent from an older backend. */
60
+ fileName?: string | null;
59
61
  };
60
62
  /** SOLDOC-2/3: absent means a requirements document. */
61
63
  isRequirementsDoc?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@popoverai/dotrequirements",
3
- "version": "0.34.0",
3
+ "version": "0.34.1",
4
4
  "description": "Requirements as testable, human-readable data — CLI and test harness for spec-driven development with AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {