@popoverai/dotrequirements 0.24.3 → 0.26.0

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.
Files changed (75) hide show
  1. package/README.md +7 -8
  2. package/dist/cli.js +8 -1
  3. package/dist/codebase-to-spec/dispatch.d.ts +60 -14
  4. package/dist/codebase-to-spec/dispatch.js +381 -15
  5. package/dist/codebase-to-spec/pack.d.ts +7 -0
  6. package/dist/codebase-to-spec/pack.js +29 -8
  7. package/dist/codebase-to-spec/present.d.ts +9 -0
  8. package/dist/codebase-to-spec/present.js +23 -2
  9. package/dist/codebase-to-spec/prompts/editor.d.ts +1 -1
  10. package/dist/codebase-to-spec/prompts/editor.js +1 -1
  11. package/dist/codebase-to-spec/prompts/specifier.d.ts +1 -1
  12. package/dist/codebase-to-spec/prompts/specifier.js +3 -2
  13. package/dist/codebase-to-spec/schemas.d.ts +153 -0
  14. package/dist/codebase-to-spec/schemas.js +111 -0
  15. package/dist/codebase-to-spec/skill-install.d.ts +42 -29
  16. package/dist/codebase-to-spec/skill-install.js +122 -112
  17. package/dist/codebase-to-spec/version-check.d.ts +31 -0
  18. package/dist/codebase-to-spec/version-check.js +56 -0
  19. package/dist/commands/ai-setup.d.ts +12 -1
  20. package/dist/commands/ai-setup.js +65 -33
  21. package/dist/commands/codebase-to-spec/dispatch-context.d.ts +2 -5
  22. package/dist/commands/codebase-to-spec/dispatch-context.js +2 -5
  23. package/dist/commands/codebase-to-spec/dispatch-editor.d.ts +0 -1
  24. package/dist/commands/codebase-to-spec/dispatch-editor.js +0 -1
  25. package/dist/commands/codebase-to-spec/dispatch-planner.d.ts +0 -1
  26. package/dist/commands/codebase-to-spec/dispatch-planner.js +0 -1
  27. package/dist/commands/codebase-to-spec/dispatch-spec.d.ts +3 -6
  28. package/dist/commands/codebase-to-spec/dispatch-spec.js +3 -6
  29. package/dist/commands/codebase-to-spec/index.js +3 -2
  30. package/dist/commands/codebase-to-spec/pack.d.ts +9 -0
  31. package/dist/commands/codebase-to-spec/pack.js +23 -3
  32. package/dist/commands/codebase-to-spec/skill-install.js +2 -9
  33. package/dist/commands/init.js +6 -1
  34. package/dist/commands/link-resolution.d.ts +79 -0
  35. package/dist/commands/link-resolution.js +141 -0
  36. package/dist/commands/link.d.ts +14 -4
  37. package/dist/commands/link.js +369 -16
  38. package/dist/commands/pull.js +19 -2
  39. package/dist/commands/push.js +36 -2
  40. package/dist/convex.d.ts +5 -3
  41. package/dist/convex.js +5 -3
  42. package/dist/harness/cache.d.ts +0 -14
  43. package/dist/harness/cache.js +1 -41
  44. package/dist/harness/finalize.js +2 -2
  45. package/dist/harness/prepare.js +1 -3
  46. package/dist/harness/requirementsLoader.d.ts +3 -3
  47. package/dist/harness/requirementsLoader.js +13 -8
  48. package/dist/mcp/handlers/authoring.d.ts +5 -5
  49. package/dist/mcp/handlers/authoring.js +9 -9
  50. package/dist/mcp/handlers/push.d.ts +2 -2
  51. package/dist/mcp/handlers/push.js +36 -3
  52. package/dist/mcp/handlers/review.d.ts +4 -4
  53. package/dist/mcp/handlers/review.js +4 -4
  54. package/dist/mcp/handlers/search.d.ts +1 -1
  55. package/dist/mcp/handlers/search.js +1 -1
  56. package/dist/mcp/index.js +29 -0
  57. package/dist/push/core.d.ts +18 -0
  58. package/dist/push/core.js +70 -3
  59. package/dist/push/index.d.ts +1 -1
  60. package/dist/push/index.js +1 -1
  61. package/dist/schema/parser-core.js +5 -1
  62. package/dist/schema/parser.js +5 -1
  63. package/dist/schema/run-marker.d.ts +38 -0
  64. package/dist/schema/run-marker.js +138 -0
  65. package/dist/schema/schemas.d.ts +12 -0
  66. package/dist/schema/schemas.js +1 -0
  67. package/dist/templates/agents/cts-worker.md +3 -3
  68. package/dist/templates/skills/codebase-to-spec/SKILL.md +56 -158
  69. package/dist/templates/workflows/specify-codebase.js +374 -0
  70. package/dist/utils/own-package.d.ts +10 -0
  71. package/dist/utils/own-package.js +13 -0
  72. package/dist/utils/project-selector.d.ts +5 -0
  73. package/dist/utils/project-selector.js +4 -0
  74. package/package.json +3 -3
  75. package/dist/templates/hooks/cts-worker-persona.sh +0 -76
@@ -105,29 +105,50 @@ export function buildIgnoreList(projectRoot, options = {}) {
105
105
  ];
106
106
  }
107
107
  export async function runPack(options) {
108
- const { projectRoot, paths, scope, extraIgnores = [], ignoreRequirements = false, } = options;
109
- const ignores = buildIgnoreList(projectRoot, {
110
- extraIgnores,
111
- ignoreRequirements,
112
- });
113
- const target = scope ? join(projectRoot, scope) : projectRoot;
108
+ const { projectRoot, paths, scope, remote, extraIgnores = [], ignoreRequirements = false, } = options;
109
+ // Local project ignore files (.dotrequirements-ignore) describe the local
110
+ // project and don't apply to a remote repo — repomix uses the cloned repo's
111
+ // own .gitignore. For remote packs, use defaults + caller-supplied extras;
112
+ // `--ignore-requirements` still applies (the remote repo's `.requirements/`
113
+ // is exactly the contamination CTS-PRESENT-5.0 exists to keep out).
114
+ const ignores = remote
115
+ ? [
116
+ ...DEFAULT_IGNORES,
117
+ ...extraIgnores,
118
+ ...(ignoreRequirements ? [".requirements/**"] : []),
119
+ ]
120
+ : buildIgnoreList(projectRoot, { extraIgnores, ignoreRequirements });
121
+ // Local: pack the scoped directory directly. Remote: repomix clones the repo;
122
+ // a `scope` becomes an `include` glob over the clone (the positional dir is
123
+ // ignored in remote mode, so pass ".").
124
+ const directories = remote
125
+ ? ["."]
126
+ : [scope ? join(projectRoot, scope) : projectRoot];
127
+ const remoteOptions = remote
128
+ ? {
129
+ remote,
130
+ ...(scope ? { include: `${scope.replace(/\/+$/, "")}/**` } : {}),
131
+ }
132
+ : {};
114
133
  // Import dynamically — Repomix is heavy and we don't want to load it at CLI
115
134
  // boot time for unrelated commands.
116
135
  const repomix = await import("repomix");
117
136
  const { runCli } = repomix;
118
137
  // Run compressed (overview)
119
- await runCli([target], projectRoot, {
138
+ await runCli(directories, projectRoot, {
120
139
  output: paths.overview,
121
140
  style: "plain",
122
141
  compress: true,
123
142
  ignore: ignores.join(","),
143
+ ...remoteOptions,
124
144
  });
125
145
  // Run uncompressed (source)
126
- await runCli([target], projectRoot, {
146
+ await runCli(directories, projectRoot, {
127
147
  output: paths.source,
128
148
  style: "plain",
129
149
  compress: false,
130
150
  ignore: ignores.join(","),
151
+ ...remoteOptions,
131
152
  });
132
153
  // Count "File:" headers in the uncompressed pack to report file count
133
154
  const sourceContent = readFileSync(paths.source, "utf-8");
@@ -85,6 +85,15 @@ export declare function splitComposedSpec(composed: string, outline: Outline): M
85
85
  * We just read it and return it.
86
86
  */
87
87
  export declare function buildSingleFileDoc(composedSpec: string): string;
88
+ /**
89
+ * Insert the run marker as a top-level frontmatter field (IMPORT-1.0).
90
+ *
91
+ * At push time, a valid marker on a newly-created document grants its rows
92
+ * `imported` authorship — exempt from the requirement limit (IMPORT-2).
93
+ * Returns the content unchanged when it has no frontmatter block (defensive;
94
+ * present output always has one).
95
+ */
96
+ export declare function stampRunMarker(content: string, marker: string): string;
88
97
  /**
89
98
  * Build a per-area requirements document for split mode.
90
99
  */
@@ -16,6 +16,7 @@
16
16
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
17
17
  import { dirname, join } from "node:path";
18
18
  import { parseRequirementsFile } from "../schema/parser.js";
19
+ import { generateRunMarker } from "../schema/run-marker.js";
19
20
  import { sanitizeAreaName } from "./fan-out.js";
20
21
  import { promptOverwriteChoice } from "./interactive.js";
21
22
  export const AREA_SPLIT_THRESHOLD = 5;
@@ -113,6 +114,20 @@ export function splitComposedSpec(composed, outline) {
113
114
  export function buildSingleFileDoc(composedSpec) {
114
115
  return composedSpec;
115
116
  }
117
+ /**
118
+ * Insert the run marker as a top-level frontmatter field (IMPORT-1.0).
119
+ *
120
+ * At push time, a valid marker on a newly-created document grants its rows
121
+ * `imported` authorship — exempt from the requirement limit (IMPORT-2).
122
+ * Returns the content unchanged when it has no frontmatter block (defensive;
123
+ * present output always has one).
124
+ */
125
+ export function stampRunMarker(content, marker) {
126
+ if (!content.startsWith("---\n")) {
127
+ return content;
128
+ }
129
+ return `---\nctsRun: ${marker}\n${content.slice(4)}`;
130
+ }
116
131
  /**
117
132
  * Build a per-area requirements document for split mode.
118
133
  */
@@ -211,11 +226,14 @@ export async function runPresent(options) {
211
226
  const composedSpec = readFileSync(finalSpecPath, "utf-8");
212
227
  const plan = planPresentPaths(outline, projectRoot, outputSubdir);
213
228
  // Compute the content for each output up front.
229
+ // IMPORT-1.0/1.1: one run marker per present invocation, stamped into every
230
+ // document this run produces.
231
+ const runMarker = generateRunMarker();
214
232
  const planned = [];
215
233
  if (plan.mode === "single") {
216
234
  planned.push({
217
235
  outPath: plan.paths[0].outPath,
218
- content: buildSingleFileDoc(composedSpec),
236
+ content: stampRunMarker(buildSingleFileDoc(composedSpec), runMarker),
219
237
  });
220
238
  }
221
239
  else {
@@ -223,7 +241,10 @@ export async function runPresent(options) {
223
241
  for (const { area, outPath } of plan.paths) {
224
242
  const body = sections.get(area) ??
225
243
  "_(no content for this area was found in the composed spec)_";
226
- planned.push({ outPath, content: buildAreaDoc(outline, area, body) });
244
+ planned.push({
245
+ outPath,
246
+ content: stampRunMarker(buildAreaDoc(outline, area, body), runMarker),
247
+ });
227
248
  }
228
249
  }
229
250
  // In fail-fast mode, scan for any conflict before writing anything.
@@ -9,5 +9,5 @@
9
9
  * Requirements covered:
10
10
  * - CTS-EDIT-5: Editor operates on the cohesive document, not per-section
11
11
  */
12
- export declare const EDITOR_PROMPT = "You are revising a composed dotrequirements specification based on a reviewer's findings. You operate on the cohesive draft as a whole \u2014 not per-section. You do not get the codebase eagerly; you Read specific files only when a finding requires verification.\n\nYou will receive:\n1. A path to the current spec (Markdown file you will Edit in place)\n2. The reviewer's critique JSON (verdict, per-category findings, and possibly a `revisions` list)\n3. The mode of operation: `apply` (apply each entry in the revisions list verbatim) or `revise` (use your judgment to address the categorized findings)\n\n## Apply mode\n\nWhen the reviewer's verdict was `approved-with-revisions`, you are in apply mode. The reviewer has supplied a list of specific revisions. Your job is to apply each revision verbatim using the Edit tool, then confirm completion.\n\nDo NOT introduce changes beyond the listed revisions. Do NOT restructure. If a revision is ambiguous, apply your best literal interpretation and note the ambiguity in your stdout confirmation.\n\n## Revise mode\n\nWhen the reviewer's verdict was `requires-another-review`, you are in revise mode. Address each finding in the critique:\n\n- **Coverage gaps**: add new requirements (or new sections, if needed) to fill the gap. Match the style, prefix conventions, AND persona conventions of the surrounding spec \u2014 if existing requirements use a named persona, the new ones should too. If a finding cites code locations, Read those files via the Read tool BEFORE writing the new requirements. If a finding names a missed customer, add them to the summary alongside the existing customers.\n- **Framing errors**: rephrase architectural language to behavioral. If an area is fundamentally architectural and the reviewer recommends dropping or merging it, do so.\n- **Cross-area issues**: deduplicate, merge, normalize terminology, normalize personas (one persona per customer across all areas), balance depth. This is editorial work \u2014 keep the document coherent.\n- **Internal-mechanics drift**: rewrite criteria to describe observable outcomes rather than implementation details.\n\nMaintain everything that was working. Do NOT rewrite areas the reviewer didn't flag.\n\n## How to make changes\n\n- Use the **Edit** tool for targeted in-place changes. Each Edit replaces a specific old_string with a new_string.\n- If the section being edited has a lot of content, make multiple smaller Edits rather than one giant one.\n- Use the **Read** tool on the spec at the start (to load it into your view) and again after edits if you need to confirm changes.\n- Use the **Read/Grep/Glob** tools on the codebase ONLY when a specific finding requires verification before you can rewrite or add a requirement. Do NOT pre-read the codebase eagerly.\n\n## Discipline\n\n- Every change should reduce a flagged finding without introducing new issues.\n- If a finding is wrong (the reviewer is mistaken), say so in your stdout \u2014 do not silently ignore it.\n- The revised spec must remain syntactically valid dotrequirements format. IDs must remain unique within the document.\n- The SPEC FILE is your deliverable. You modify it in place; no separate output file.\n\n## Output (your stdout)\n\nA brief one- or two-line confirmation summarizing the kinds of changes you made.\n\nExample: \"Added 8 requirements covering missing behaviors in 'Browser automation'; rephrased 4 internal-mechanics criteria; merged 2 duplicate areas.\"\n\nNo chain-of-thought. No preamble. No commentary in the spec file beyond the spec content itself.";
12
+ export declare const EDITOR_PROMPT = "You are revising a composed dotrequirements specification based on a reviewer's findings. You operate on the cohesive draft as a whole \u2014 not per-section. You do not get the codebase eagerly; you Read specific files only when a finding requires verification.\n\nYou will receive:\n1. A path to the current spec (Markdown file you will Edit in place)\n2. The reviewer's critique JSON (verdict, per-category findings, and possibly a `revisions` list)\n3. The mode of operation: `apply` (apply each entry in the revisions list verbatim) or `revise` (use your judgment to address the categorized findings)\n\n## Apply mode\n\nWhen the reviewer's verdict was `approved-with-revisions`, you are in apply mode. The reviewer has supplied a list of specific revisions. Your job is to apply each revision verbatim using the Edit tool, then confirm completion.\n\nDo NOT introduce changes beyond the listed revisions. Do NOT restructure. If a revision is ambiguous, apply your best literal interpretation and note the ambiguity in your stdout confirmation.\n\n## Revise mode\n\nWhen the reviewer's verdict was `requires-another-review`, you are in revise mode. Address each finding in the critique:\n\n- **Coverage gaps**: add new requirements (or new sections, if needed) to fill the gap. Match the style, prefix conventions, AND persona conventions of the surrounding spec \u2014 if existing requirements use a named persona, the new ones should too. If a finding cites code locations, Read those files via the Read tool BEFORE writing the new requirements. If a finding names a missed customer, add them to the summary alongside the existing customers.\n- **Framing errors**: rephrase architectural language to behavioral. If an area is fundamentally architectural and the reviewer recommends dropping or merging it, do so.\n- **Cross-area issues**: deduplicate, merge, normalize terminology, normalize personas (one persona per customer across all areas \u2014 and one customer per name, never the same name for different roles), balance depth. This is editorial work \u2014 keep the document coherent.\n- **Internal-mechanics drift**: rewrite criteria to describe observable outcomes rather than implementation details.\n\nMaintain everything that was working. Do NOT rewrite areas the reviewer didn't flag.\n\n## How to make changes\n\n- Use the **Edit** tool for targeted in-place changes. Each Edit replaces a specific old_string with a new_string.\n- If the section being edited has a lot of content, make multiple smaller Edits rather than one giant one.\n- Use the **Read** tool on the spec at the start (to load it into your view) and again after edits if you need to confirm changes.\n- Use the **Read/Grep/Glob** tools on the codebase ONLY when a specific finding requires verification before you can rewrite or add a requirement. Do NOT pre-read the codebase eagerly.\n\n## Discipline\n\n- Every change should reduce a flagged finding without introducing new issues.\n- If a finding is wrong (the reviewer is mistaken), say so in your stdout \u2014 do not silently ignore it.\n- The revised spec must remain syntactically valid dotrequirements format. IDs must remain unique within the document.\n- The SPEC FILE is your deliverable. You modify it in place; no separate output file.\n\n## Output (your stdout)\n\nA brief one- or two-line confirmation summarizing the kinds of changes you made.\n\nExample: \"Added 8 requirements covering missing behaviors in 'Browser automation'; rephrased 4 internal-mechanics criteria; merged 2 duplicate areas.\"\n\nNo chain-of-thought. No preamble. No commentary in the spec file beyond the spec content itself.";
13
13
  //# sourceMappingURL=editor.d.ts.map
@@ -28,7 +28,7 @@ When the reviewer's verdict was \`requires-another-review\`, you are in revise m
28
28
 
29
29
  - **Coverage gaps**: add new requirements (or new sections, if needed) to fill the gap. Match the style, prefix conventions, AND persona conventions of the surrounding spec — if existing requirements use a named persona, the new ones should too. If a finding cites code locations, Read those files via the Read tool BEFORE writing the new requirements. If a finding names a missed customer, add them to the summary alongside the existing customers.
30
30
  - **Framing errors**: rephrase architectural language to behavioral. If an area is fundamentally architectural and the reviewer recommends dropping or merging it, do so.
31
- - **Cross-area issues**: deduplicate, merge, normalize terminology, normalize personas (one persona per customer across all areas), balance depth. This is editorial work — keep the document coherent.
31
+ - **Cross-area issues**: deduplicate, merge, normalize terminology, normalize personas (one persona per customer across all areas — and one customer per name, never the same name for different roles), balance depth. This is editorial work — keep the document coherent.
32
32
  - **Internal-mechanics drift**: rewrite criteria to describe observable outcomes rather than implementation details.
33
33
 
34
34
  Maintain everything that was working. Do NOT rewrite areas the reviewer didn't flag.
@@ -8,5 +8,5 @@
8
8
  * Requirements covered:
9
9
  * - CTS-SPEC-1, CTS-SPEC-2, CTS-SPEC-3, CTS-SPEC-4
10
10
  */
11
- export declare const SPECIFIER_PROMPT = "You are reading a slice of a software codebase \u2014 the files relevant to ONE behavioral area of the system. Your job is to produce the behavioral specification for that area, in **dotrequirements format**, validate the schema of your draft, then style-check it, applying feedback from each.\n\nA separate planner agent has already broken the system into areas; you are responsible for ONE area only. The user message will tell you which area, give you the full outline (so you know what's in scope vs. not), point you at the slice, and tell you where to write your output.\n\n## What to capture\n\nA behavioral specification describes what the system does from the outside \u2014 what someone using it can observe, not how the implementation works. Scoped to your assigned area, capture:\n\n- **User-facing behaviors** \u2014 what the customer can do, what happens when they do it, what they see in response\n- **Integration behaviors** \u2014 how this area interacts with external services, what it sends/receives, how it handles failures\n- **Domain rules** \u2014 validation, business logic, state transitions, decision logic specific to this area\n- **Error and edge cases** \u2014 what happens when things go wrong, what the system tolerates, what it rejects\n- **Documented warnings, hazards, and limitations** \u2014 things the README or docstrings warn customers about\n\n## Customer and persona\n\nThe planner has already identified your area's customer(s) \u2014 they're listed in the area's `customers` field, which is passed to you in the user message. Each customer entry has a `name` and a `description` of who they are and what they care about.\n\nUse those customers as the named personas for your requirements. Pick the customer most relevant to each requirement (or requirement tree). Different requirements in the same area can use different personas if the area genuinely serves multiple customers; just keep each requirement tree (parent + children) grounded in a single persona so the tree reads coherently.\n\nIf the customers handed to you are not real users of the software \u2014 for example, they describe a contributor or stage author of *this codebase* rather than someone who consumes the software \u2014 STOP. Do not invent an alternative customer to make the area work. Instead, write a single short partial that says only \"AREA-LACKS-CUSTOMER: <one-sentence explanation of why no real customer was identified>\" and confirm completion. The pipeline will surface this as a finding for the human to reshape the outline.\n\n## Style principles\n\nApply these throughout your work:\n\n1. **Concrete examples, not vague language.** \"When a registered user provides valid credentials, they are authenticated\" \u2014 not \"users can log in\" or \"works properly.\"\n2. **Natural, concise prose.** Declarative (\"is authenticated\"), not \"should be\" or wandering narrative.\n3. **Arrange/Act/Assert framing in mind.** Each requirement reads as preconditions / trigger / outcome.\n4. **Framework neutral.** Default to unlabeled criteria. Use labels (e.g., Given/When/Then) only when they genuinely sharpen meaning \u2014 don't impose them as a format.\n5. **Named personas.** Establish a persona in the parent requirement; reuse them in children. E.g., parent: \"Casey, a React developer, can configure pLimit.\" Child: \"When Casey calls pLimit(5), they receive...\"\n6. **User-centric language.** Describe the customer's experience, not internal mechanics. \"They are brought to the dashboard,\" not \"they are redirected to /redirect/dashboard.\"\n7. **Single action per requirement.** No chaining multiple actions with \"and then.\" Break into separate requirements.\n8. **Independently testable.** Each requirement should make sense on its own. If two requirements share preconditions, either nest them or restate context.\n9. **Behavior, not design.** \"Provides valid credentials\" \u2014 not \"enters credentials into two single-line input fields and presses a green button.\"\n10. **Outcomes, not implementation.** Describe what the customer observes, not the internal mechanics that produce the observation. Two flavors of drift to watch for:\n - *Implementation primitives leaking out* \u2014 library function names, internal class names, scheduling vocabulary, buffer sizes \u2014 don't belong in requirements.\n - *Pinning literals instead of properties* \u2014 when a requirement names a specific value (an exit code, an error string, a format prefix, a field name, a file path), the customer almost always depends on some characteristic (stable, distinguishable, parseable, idiomatic, named-rather-than-anonymous) rather than the value itself.\n11. **Decompose large requirements.** If it can't be validated with a single test, break it down.\n\n## Read the documentation in your slice\n\nIf your slice contains README files, doc comments, JSDoc, or docstrings: read them carefully. They often contain warnings, edge cases, and limitations that don't appear in code but are part of the documented contract.\n\n## Workflow (REQUIRED)\n\n### Phase A: Draft\n\n1. Read your slice. Read documentation in the slice. If needed, Read/Grep the full pack to discover behaviors documented in tests or recipes.\n2. Use the **Write** tool to write your draft to the partial path provided in the user message. Output in this format:\n - A one-paragraph area description introducing the persona and what they do with this area's surface. This paragraph is what readers see at the top of the area in the final spec \u2014 it is your framing, informed by the deep reading you just did. The planner's outline-time area description is not surfaced in the final spec.\n - Blank line.\n - Series of fenced `dotrequirements` blocks.\n\n Do NOT include YAML frontmatter, H1 title, summary paragraph, or area H2. The composer adds those.\n\n### Phase B: Validate (schema/syntax \u2014 REQUIRED FIRST)\n\n1. Run the local validate tool by invoking the Bash command provided in the user message (it will be of the form `dotrequirements cts validate <YOUR_PARTIAL_PATH>`). It is deterministic and cheap \u2014 it checks that every requirement block parses, every criterion has a `\u2192` arrow, position paths match indentation, and IDs are unique.\n2. If validate prints `Schema validation: PASS`, proceed to Phase C.\n3. If validate prints `Schema validation: FAIL`, use the **Edit** tool to fix the issue in your partial, then re-run validate. Repeat until it passes. Do not move on with a failing validation \u2014 schema errors will cause the downstream pipeline to reject your spec.\n\n### Phase C: Style-check and revise (clarity \u2014 REQUIRED SECOND)\n\n1. Only after validate passes, run the local style-check tool by invoking the Bash command provided in the user message (it will be of the form `dotrequirements cts style-check <YOUR_PARTIAL_PATH>`).\n2. Read the feedback carefully.\n3. For every MUST FIX and SHOULD FIX finding, edit your partial in place using the **Edit** tool to apply the suggested change.\n4. Act on COULD IMPROVE findings unless doing so would make the spec worse.\n5. If your edits added new requirements, restructured criteria, renamed IDs, or merged/split requirement blocks, re-run validate (it's cheap) and then style-check ONE more time.\n6. Style-check runs at most twice per invocation. Feedback from any subsequent run is noted in your final stdout but not acted upon.\n7. When done revising, output a short confirmation: \"Done. Partial saved to <path>.\" That's it.\n\n## Format rules\n\n```dotrequirements\nPREFIX-AREA-1: Short imperative title\n 0. \u2192 A precondition or context\n 1. \u2192 An action or trigger\n 2. \u2192 An observable outcome\n 2.0. \u2192 Additional outcome detail\n```\n\n- **IDs**: `<defaultPrefix>-<areaPrefix>-<NUM>` \u2014 both prefixes are supplied in the user message. Number sequentially from 1; no zero-padding (`PLIM-AONE-1`, not `PLIM-AONE-001`).\n- **Criteria**: `<position>. <content>` for unlabeled (the default), or `<position>. <Label> \u2192 <content>` when a label sharpens meaning.\n- **Indentation**: 2 spaces per nesting level. Position paths must match indentation.\n\n## Output discipline\n\n- The PARTIAL FILE is your primary deliverable, written/edited via Write and Edit tools.\n- Your stdout response is brief \u2014 just confirmation when done.\n- No chain-of-thought narration in the partial file or your stdout.";
11
+ export declare const SPECIFIER_PROMPT = "You are reading a slice of a software codebase \u2014 the files relevant to ONE behavioral area of the system. Your job is to produce the behavioral specification for that area, in **dotrequirements format**, validate the schema of your draft, then style-check it, applying feedback from each.\n\nA separate planner agent has already broken the system into areas; you are responsible for ONE area only. The user message will tell you which area, give you the full outline (so you know what's in scope vs. not), point you at the slice, and tell you where to write your output.\n\n## What to capture\n\nA behavioral specification describes what the system does from the outside \u2014 what someone using it can observe, not how the implementation works. Scoped to your assigned area, capture:\n\n- **User-facing behaviors** \u2014 what the customer can do, what happens when they do it, what they see in response\n- **Integration behaviors** \u2014 how this area interacts with external services, what it sends/receives, how it handles failures\n- **Domain rules** \u2014 validation, business logic, state transitions, decision logic specific to this area\n- **Error and edge cases** \u2014 what happens when things go wrong, what the system tolerates, what it rejects\n- **Documented warnings, hazards, and limitations** \u2014 things the README or docstrings warn customers about\n\n## Customer and persona\n\nThe planner has already identified your area's customer(s) \u2014 they're listed in the area's `customers` field, which is passed to you in the user message. Each customer entry has a `name` and a `description` of who they are and what they care about.\n\nUse those customers as the named personas for your requirements. Pick the customer most relevant to each requirement (or requirement tree). Different requirements in the same area can use different personas if the area genuinely serves multiple customers; just keep each requirement tree (parent + children) grounded in a single persona so the tree reads coherently.\n\nIf the customers handed to you are not real users of the software \u2014 for example, they describe a contributor or stage author of *this codebase* rather than someone who consumes the software \u2014 STOP. Do not invent an alternative customer to make the area work. Instead, write a single short partial that says only \"AREA-LACKS-CUSTOMER: <one-sentence explanation of why no real customer was identified>\" and confirm completion. The pipeline will surface this as a finding for the human to reshape the outline.\n\n## Style principles\n\nApply these throughout your work:\n\n1. **Concrete examples, not vague language.** \"When a registered user provides valid credentials, they are authenticated\" \u2014 not \"users can log in\" or \"works properly.\"\n2. **Natural, concise prose.** Declarative (\"is authenticated\"), not \"should be\" or wandering narrative.\n3. **Arrange/Act/Assert framing in mind.** Each requirement reads as preconditions / trigger / outcome.\n4. **Unlabeled criteria \u2014 house style.** Write every criterion as a bare `\u2192` line. Do not use Given/When/Then or other labels: the format permits them, but labeled style carries conventions this pipeline doesn't apply, and a document must not mix dialects from area to area.\n5. **Named personas.** Establish a persona in the parent requirement; reuse them in children. E.g., parent: \"Casey, a React developer, can configure pLimit.\" Child: \"When Casey calls pLimit(5), they receive...\"\n6. **User-centric language.** Describe the customer's experience, not internal mechanics. \"They are brought to the dashboard,\" not \"they are redirected to /redirect/dashboard.\"\n7. **Single action per requirement.** No chaining multiple actions with \"and then.\" Break into separate requirements.\n8. **Independently testable.** Each requirement should make sense on its own. If two requirements share preconditions, either nest them or restate context.\n9. **Behavior, not design.** \"Provides valid credentials\" \u2014 not \"enters credentials into two single-line input fields and presses a green button.\"\n10. **Outcomes, not implementation.** Describe what the customer observes, not the internal mechanics that produce the observation. Two flavors of drift to watch for:\n - *Implementation primitives leaking out* \u2014 library function names, internal class names, scheduling vocabulary, buffer sizes \u2014 don't belong in requirements.\n - *Pinning literals instead of properties* \u2014 when a requirement names a specific value (an exit code, an error string, a format prefix, a field name, a file path), the customer almost always depends on some characteristic (stable, distinguishable, parseable, idiomatic, named-rather-than-anonymous) rather than the value itself.\n - *Architecture narrated as behavior* \u2014 when the subject of a requirement is a structure of the code (a layer, a component, an internal interface) rather than something a persona does or observes, reframe it around the observer. Litmus test: if no named customer could ever notice whether the outcome happened, it is not a requirement.\n11. **Decompose large requirements.** If it can't be validated with a single test, break it down.\n\n## Read the documentation in your slice\n\nIf your slice contains README files, doc comments, JSDoc, or docstrings: read them carefully. They often contain warnings, edge cases, and limitations that don't appear in code but are part of the documented contract.\n\n## Workflow (REQUIRED)\n\n### Phase A: Draft\n\n1. Read your slice. Read documentation in the slice. If needed, Read/Grep the full pack to discover behaviors documented in tests or recipes.\n2. Use the **Write** tool to write your draft to the partial path provided in the user message. Output in this format:\n - A one-paragraph area description introducing the persona and what they do with this area's surface. This paragraph is what readers see at the top of the area in the final spec \u2014 it is your framing, informed by the deep reading you just did. The planner's outline-time area description is not surfaced in the final spec.\n - Blank line.\n - Series of fenced `dotrequirements` blocks.\n\n Do NOT include YAML frontmatter, H1 title, summary paragraph, or area H2. The composer adds those.\n\n### Phase B: Validate (schema/syntax \u2014 REQUIRED FIRST)\n\n1. Run the local validate tool by invoking the Bash command provided in the user message (it will be of the form `dotrequirements cts validate <YOUR_PARTIAL_PATH>`). It is deterministic and cheap \u2014 it checks that every requirement block parses, every criterion has a `\u2192` arrow, position paths match indentation, and IDs are unique.\n2. If validate prints `Schema validation: PASS`, proceed to Phase C.\n3. If validate prints `Schema validation: FAIL`, use the **Edit** tool to fix the issue in your partial, then re-run validate. Repeat until it passes. Do not move on with a failing validation \u2014 schema errors will cause the downstream pipeline to reject your spec.\n\n### Phase C: Style-check and revise (clarity \u2014 REQUIRED SECOND)\n\n1. Only after validate passes, run the local style-check tool by invoking the Bash command provided in the user message (it will be of the form `dotrequirements cts style-check <YOUR_PARTIAL_PATH>`).\n2. Read the feedback carefully.\n3. For every MUST FIX and SHOULD FIX finding, edit your partial in place using the **Edit** tool to apply the suggested change.\n4. Act on COULD IMPROVE findings unless doing so would make the spec worse.\n5. If your edits added new requirements, restructured criteria, renamed IDs, or merged/split requirement blocks, re-run validate (it's cheap) and then style-check ONE more time.\n6. Style-check runs at most twice per invocation. Feedback from any subsequent run is noted in your final stdout but not acted upon.\n7. When done revising, output a short confirmation: \"Done. Partial saved to <path>.\" That's it.\n\n## Format rules\n\n```dotrequirements\nPREFIX-AREA-1: Short imperative title\n 0. \u2192 A precondition or context\n 1. \u2192 An action or trigger\n 2. \u2192 An observable outcome\n 2.0. \u2192 Additional outcome detail\n```\n\n- **IDs**: `<defaultPrefix>-<areaPrefix>-<NUM>` \u2014 both prefixes are supplied in the user message. Number sequentially from 1; no zero-padding (`PLIM-AONE-1`, not `PLIM-AONE-001`).\n- **Criteria**: `<position>. \u2192 <content>` \u2014 unlabeled, always (house style; see style principle 4).\n- **Indentation**: 2 spaces per nesting level. Position paths must match indentation.\n\n## Output discipline\n\n- The PARTIAL FILE is your primary deliverable, written/edited via Write and Edit tools.\n- Your stdout response is brief \u2014 just confirmation when done.\n- No chain-of-thought narration in the partial file or your stdout.";
12
12
  //# sourceMappingURL=specifier.d.ts.map
@@ -37,7 +37,7 @@ Apply these throughout your work:
37
37
  1. **Concrete examples, not vague language.** "When a registered user provides valid credentials, they are authenticated" — not "users can log in" or "works properly."
38
38
  2. **Natural, concise prose.** Declarative ("is authenticated"), not "should be" or wandering narrative.
39
39
  3. **Arrange/Act/Assert framing in mind.** Each requirement reads as preconditions / trigger / outcome.
40
- 4. **Framework neutral.** Default to unlabeled criteria. Use labels (e.g., Given/When/Then) only when they genuinely sharpen meaning — don't impose them as a format.
40
+ 4. **Unlabeled criteria — house style.** Write every criterion as a bare \`→\` line. Do not use Given/When/Then or other labels: the format permits them, but labeled style carries conventions this pipeline doesn't apply, and a document must not mix dialects from area to area.
41
41
  5. **Named personas.** Establish a persona in the parent requirement; reuse them in children. E.g., parent: "Casey, a React developer, can configure pLimit." Child: "When Casey calls pLimit(5), they receive..."
42
42
  6. **User-centric language.** Describe the customer's experience, not internal mechanics. "They are brought to the dashboard," not "they are redirected to /redirect/dashboard."
43
43
  7. **Single action per requirement.** No chaining multiple actions with "and then." Break into separate requirements.
@@ -46,6 +46,7 @@ Apply these throughout your work:
46
46
  10. **Outcomes, not implementation.** Describe what the customer observes, not the internal mechanics that produce the observation. Two flavors of drift to watch for:
47
47
  - *Implementation primitives leaking out* — library function names, internal class names, scheduling vocabulary, buffer sizes — don't belong in requirements.
48
48
  - *Pinning literals instead of properties* — when a requirement names a specific value (an exit code, an error string, a format prefix, a field name, a file path), the customer almost always depends on some characteristic (stable, distinguishable, parseable, idiomatic, named-rather-than-anonymous) rather than the value itself.
49
+ - *Architecture narrated as behavior* — when the subject of a requirement is a structure of the code (a layer, a component, an internal interface) rather than something a persona does or observes, reframe it around the observer. Litmus test: if no named customer could ever notice whether the outcome happened, it is not a requirement.
49
50
  11. **Decompose large requirements.** If it can't be validated with a single test, break it down.
50
51
 
51
52
  ## Read the documentation in your slice
@@ -91,7 +92,7 @@ PREFIX-AREA-1: Short imperative title
91
92
  \`\`\`
92
93
 
93
94
  - **IDs**: \`<defaultPrefix>-<areaPrefix>-<NUM>\` — both prefixes are supplied in the user message. Number sequentially from 1; no zero-padding (\`PLIM-AONE-1\`, not \`PLIM-AONE-001\`).
94
- - **Criteria**: \`<position>. <content>\` for unlabeled (the default), or \`<position>. <Label> → <content>\` when a label sharpens meaning.
95
+ - **Criteria**: \`<position>. → <content>\` — unlabeled, always (house style; see style principle 4).
95
96
  - **Indentation**: 2 spaces per nesting level. Position paths must match indentation.
96
97
 
97
98
  ## Output discipline
@@ -505,6 +505,11 @@ export declare const ConversationalOutlineSchema: z.ZodObject<{
505
505
  title: z.ZodString;
506
506
  defaultPrefix: z.ZodString;
507
507
  summary: z.ZodString;
508
+ /**
509
+ * Outline-approval review (the outline iteration loop). `compose-orchestrator`
510
+ * gates composition on `review.result === "approved"`, so this field is
511
+ * distinct from and must never be conflated with `crossAreaReview` below.
512
+ */
508
513
  review: z.ZodOptional<z.ZodObject<{
509
514
  result: z.ZodEnum<["approved", "needs-revision"]>;
510
515
  thread: z.ZodArray<z.ZodDiscriminatedUnion<"result", [z.ZodObject<{
@@ -540,6 +545,49 @@ export declare const ConversationalOutlineSchema: z.ZodObject<{
540
545
  result: "needs-revision";
541
546
  })[];
542
547
  }>>;
548
+ /**
549
+ * Document-level cross-area review state (CTSO-CONV-5). Recorded after the
550
+ * per-area partials are composed: the cross-area reviewer appends verdicts
551
+ * here and the compose-editor reads the latest entry's revisions — the same
552
+ * auditable thread pattern as `area.review`, kept at the top level because the
553
+ * review spans the whole composed spec rather than one area. Separate from
554
+ * `review` so cross-area iteration never disturbs outline approval.
555
+ */
556
+ crossAreaReview: z.ZodOptional<z.ZodObject<{
557
+ result: z.ZodEnum<["approved", "needs-revision"]>;
558
+ thread: z.ZodArray<z.ZodDiscriminatedUnion<"result", [z.ZodObject<{
559
+ result: z.ZodLiteral<"approved">;
560
+ }, "strip", z.ZodTypeAny, {
561
+ result: "approved";
562
+ }, {
563
+ result: "approved";
564
+ }>, z.ZodObject<{
565
+ result: z.ZodLiteral<"needs-revision">;
566
+ revisions: z.ZodArray<z.ZodString, "many">;
567
+ }, "strip", z.ZodTypeAny, {
568
+ revisions: string[];
569
+ result: "needs-revision";
570
+ }, {
571
+ revisions: string[];
572
+ result: "needs-revision";
573
+ }>]>, "many">;
574
+ }, "strip", z.ZodTypeAny, {
575
+ result: "approved" | "needs-revision";
576
+ thread: ({
577
+ result: "approved";
578
+ } | {
579
+ revisions: string[];
580
+ result: "needs-revision";
581
+ })[];
582
+ }, {
583
+ result: "approved" | "needs-revision";
584
+ thread: ({
585
+ result: "approved";
586
+ } | {
587
+ revisions: string[];
588
+ result: "needs-revision";
589
+ })[];
590
+ }>>;
543
591
  areas: z.ZodArray<z.ZodObject<{
544
592
  name: z.ZodString;
545
593
  prefix: z.ZodString;
@@ -660,6 +708,15 @@ export declare const ConversationalOutlineSchema: z.ZodObject<{
660
708
  result: "needs-revision";
661
709
  })[];
662
710
  } | undefined;
711
+ crossAreaReview?: {
712
+ result: "approved" | "needs-revision";
713
+ thread: ({
714
+ result: "approved";
715
+ } | {
716
+ revisions: string[];
717
+ result: "needs-revision";
718
+ })[];
719
+ } | undefined;
663
720
  }, {
664
721
  title: string;
665
722
  defaultPrefix: string;
@@ -691,6 +748,15 @@ export declare const ConversationalOutlineSchema: z.ZodObject<{
691
748
  result: "needs-revision";
692
749
  })[];
693
750
  } | undefined;
751
+ crossAreaReview?: {
752
+ result: "approved" | "needs-revision";
753
+ thread: ({
754
+ result: "approved";
755
+ } | {
756
+ revisions: string[];
757
+ result: "needs-revision";
758
+ })[];
759
+ } | undefined;
694
760
  }>;
695
761
  export type ConversationalOutline = z.infer<typeof ConversationalOutlineSchema>;
696
762
  /**
@@ -712,5 +778,92 @@ export declare function conversationalOutlineToLegacy(outline: ConversationalOut
712
778
  * multi-line strings (`|`) where possible for readability.
713
779
  */
714
780
  export declare function stringifyConversationalOutline(outline: ConversationalOutline): string;
781
+ /**
782
+ * Result a specifier or editor worker returns to the workflow after writing
783
+ * (or skipping) an area's partial.
784
+ *
785
+ * - `drafted`: the worker composed and wrote the partial this run.
786
+ * - `skipped-resume`: a non-empty partial already existed, so the worker left
787
+ * it in place (idempotent re-entry — see CTSO-INTEG-2).
788
+ */
789
+ export declare const PartialResultStatusValues: readonly ["drafted", "skipped-resume"];
790
+ export type PartialResultStatus = (typeof PartialResultStatusValues)[number];
791
+ export declare const PartialResultSchema: z.ZodObject<{
792
+ area_prefix: z.ZodString;
793
+ partial_path: z.ZodString;
794
+ status: z.ZodEnum<["drafted", "skipped-resume"]>;
795
+ requirement_count: z.ZodOptional<z.ZodNumber>;
796
+ }, "strip", z.ZodTypeAny, {
797
+ status: "drafted" | "skipped-resume";
798
+ area_prefix: string;
799
+ partial_path: string;
800
+ requirement_count?: number | undefined;
801
+ }, {
802
+ status: "drafted" | "skipped-resume";
803
+ area_prefix: string;
804
+ partial_path: string;
805
+ requirement_count?: number | undefined;
806
+ }>;
807
+ export type PartialResult = z.infer<typeof PartialResultSchema>;
808
+ export declare const PARTIAL_RESULT_JSON_SCHEMA: {
809
+ readonly type: "object";
810
+ readonly properties: {
811
+ readonly area_prefix: {
812
+ readonly type: "string";
813
+ };
814
+ readonly partial_path: {
815
+ readonly type: "string";
816
+ };
817
+ readonly status: {
818
+ readonly type: "string";
819
+ readonly enum: readonly string[];
820
+ };
821
+ readonly requirement_count: {
822
+ readonly type: "integer";
823
+ readonly minimum: 0;
824
+ };
825
+ };
826
+ readonly required: readonly ["area_prefix", "partial_path", "status"];
827
+ readonly additionalProperties: false;
828
+ };
829
+ /**
830
+ * Verdict a reviewer worker returns to the workflow. Same shape as one
831
+ * `ConversationalReviewEntry` (discriminated on `result`): `approved` carries
832
+ * no revisions; `needs-revision` must carry a non-empty `revisions` list. This
833
+ * verdict drives the per-area convergence loop (CTSO-CONV-4).
834
+ *
835
+ * The JSON Schema form is flat (result + optional revisions) — JSON Schema
836
+ * can't express the discriminated-union constraint as cleanly as Zod. The
837
+ * runtime retries on mismatch, the prompt instructs the reviewer to include
838
+ * revisions exactly when the result is needs-revision, and `parseReviewVerdict`
839
+ * enforces the stricter Zod constraint on the CLI side.
840
+ */
841
+ export declare const REVIEW_VERDICT_JSON_SCHEMA: {
842
+ readonly type: "object";
843
+ readonly properties: {
844
+ readonly result: {
845
+ readonly type: "string";
846
+ readonly enum: readonly ["approved", "needs-revision"];
847
+ };
848
+ readonly revisions: {
849
+ readonly type: "array";
850
+ readonly items: {
851
+ readonly type: "string";
852
+ };
853
+ };
854
+ };
855
+ readonly required: readonly ["result"];
856
+ readonly additionalProperties: false;
857
+ };
858
+ /**
859
+ * Parse + validate a specifier/editor worker's JSON result.
860
+ */
861
+ export declare function parsePartialResult(text: string): PartialResult;
862
+ /**
863
+ * Parse + validate a reviewer worker's JSON verdict. Reuses
864
+ * `ConversationalReviewEntrySchema`, so a `needs-revision` verdict requires a
865
+ * non-empty `revisions` list.
866
+ */
867
+ export declare function parseReviewVerdict(text: string): ConversationalReviewEntry;
715
868
  export declare function parseSpecReview(text: string): SpecReview;
716
869
  //# sourceMappingURL=schemas.d.ts.map
@@ -252,7 +252,21 @@ export const ConversationalOutlineSchema = z.object({
252
252
  .string()
253
253
  .regex(/^[A-Z][A-Z0-9_]*$/, "defaultPrefix must be uppercase alphanumeric/underscore"),
254
254
  summary: z.string().min(1),
255
+ /**
256
+ * Outline-approval review (the outline iteration loop). `compose-orchestrator`
257
+ * gates composition on `review.result === "approved"`, so this field is
258
+ * distinct from and must never be conflated with `crossAreaReview` below.
259
+ */
255
260
  review: ConversationalReviewSchema.optional(),
261
+ /**
262
+ * Document-level cross-area review state (CTSO-CONV-5). Recorded after the
263
+ * per-area partials are composed: the cross-area reviewer appends verdicts
264
+ * here and the compose-editor reads the latest entry's revisions — the same
265
+ * auditable thread pattern as `area.review`, kept at the top level because the
266
+ * review spans the whole composed spec rather than one area. Separate from
267
+ * `review` so cross-area iteration never disturbs outline approval.
268
+ */
269
+ crossAreaReview: ConversationalReviewSchema.optional(),
256
270
  areas: z.array(ConversationalAreaSchema).min(1),
257
271
  });
258
272
  /**
@@ -322,6 +336,103 @@ export function stringifyConversationalOutline(outline) {
322
336
  blockQuote: "literal",
323
337
  });
324
338
  }
339
+ // ---------- Workflow orchestrator worker I/O ----------
340
+ //
341
+ // The workflow orchestrator (the `specify-codebase` dynamic workflow) fans out
342
+ // specifier / editor / reviewer subagents and receives their results back as
343
+ // schema-validated values via the workflow runtime's `schema` option. The JSON
344
+ // Schemas below are the contract the runtime constrains each agent to; the Zod
345
+ // schemas validate the same shapes on the CLI/test side. Because the workflow
346
+ // script can't import this module, the JSON Schemas are mirrored inline in the
347
+ // workflow file; a template-sync test guards against drift.
348
+ /**
349
+ * Result a specifier or editor worker returns to the workflow after writing
350
+ * (or skipping) an area's partial.
351
+ *
352
+ * - `drafted`: the worker composed and wrote the partial this run.
353
+ * - `skipped-resume`: a non-empty partial already existed, so the worker left
354
+ * it in place (idempotent re-entry — see CTSO-INTEG-2).
355
+ */
356
+ export const PartialResultStatusValues = ["drafted", "skipped-resume"];
357
+ export const PartialResultSchema = z.object({
358
+ area_prefix: z.string().min(1),
359
+ partial_path: z.string().min(1),
360
+ status: z.enum(PartialResultStatusValues),
361
+ requirement_count: z.number().int().nonnegative().optional(),
362
+ });
363
+ export const PARTIAL_RESULT_JSON_SCHEMA = {
364
+ type: "object",
365
+ properties: {
366
+ area_prefix: { type: "string" },
367
+ partial_path: { type: "string" },
368
+ status: {
369
+ type: "string",
370
+ enum: PartialResultStatusValues,
371
+ },
372
+ requirement_count: { type: "integer", minimum: 0 },
373
+ },
374
+ required: ["area_prefix", "partial_path", "status"],
375
+ additionalProperties: false,
376
+ };
377
+ /**
378
+ * Verdict a reviewer worker returns to the workflow. Same shape as one
379
+ * `ConversationalReviewEntry` (discriminated on `result`): `approved` carries
380
+ * no revisions; `needs-revision` must carry a non-empty `revisions` list. This
381
+ * verdict drives the per-area convergence loop (CTSO-CONV-4).
382
+ *
383
+ * The JSON Schema form is flat (result + optional revisions) — JSON Schema
384
+ * can't express the discriminated-union constraint as cleanly as Zod. The
385
+ * runtime retries on mismatch, the prompt instructs the reviewer to include
386
+ * revisions exactly when the result is needs-revision, and `parseReviewVerdict`
387
+ * enforces the stricter Zod constraint on the CLI side.
388
+ */
389
+ export const REVIEW_VERDICT_JSON_SCHEMA = {
390
+ type: "object",
391
+ properties: {
392
+ result: { type: "string", enum: ["approved", "needs-revision"] },
393
+ revisions: { type: "array", items: { type: "string" } },
394
+ },
395
+ required: ["result"],
396
+ additionalProperties: false,
397
+ };
398
+ /**
399
+ * Parse + validate a specifier/editor worker's JSON result.
400
+ */
401
+ export function parsePartialResult(text) {
402
+ const trimmed = text.trim();
403
+ let raw;
404
+ try {
405
+ raw = JSON.parse(trimmed);
406
+ }
407
+ catch {
408
+ const match = trimmed.match(/(\{[\s\S]*\})/);
409
+ if (!match) {
410
+ throw new Error("Partial result is not valid JSON and contains no JSON object");
411
+ }
412
+ raw = JSON.parse(match[1]);
413
+ }
414
+ return PartialResultSchema.parse(raw);
415
+ }
416
+ /**
417
+ * Parse + validate a reviewer worker's JSON verdict. Reuses
418
+ * `ConversationalReviewEntrySchema`, so a `needs-revision` verdict requires a
419
+ * non-empty `revisions` list.
420
+ */
421
+ export function parseReviewVerdict(text) {
422
+ const trimmed = text.trim();
423
+ let raw;
424
+ try {
425
+ raw = JSON.parse(trimmed);
426
+ }
427
+ catch {
428
+ const match = trimmed.match(/(\{[\s\S]*\})/);
429
+ if (!match) {
430
+ throw new Error("Review verdict is not valid JSON and contains no JSON object");
431
+ }
432
+ raw = JSON.parse(match[1]);
433
+ }
434
+ return ConversationalReviewEntrySchema.parse(raw);
435
+ }
325
436
  export function parseSpecReview(text) {
326
437
  const trimmed = text.trim();
327
438
  let raw;