@opengsd/gsd-core 1.6.1 → 1.7.0-rc.2

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 (119) hide show
  1. package/.claude-plugin/marketplace.json +20 -0
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.opencode/plugins/gsd-core.js +711 -0
  4. package/agents/gsd-advisor-researcher.md +2 -0
  5. package/agents/gsd-ai-researcher.md +1 -1
  6. package/agents/gsd-assumptions-analyzer.md +2 -0
  7. package/agents/gsd-code-fixer.md +2 -0
  8. package/agents/gsd-code-reviewer.md +2 -0
  9. package/agents/gsd-codebase-mapper.md +2 -0
  10. package/agents/gsd-debugger.md +2 -0
  11. package/agents/gsd-doc-writer.md +2 -0
  12. package/agents/gsd-eval-auditor.md +2 -0
  13. package/agents/gsd-executor.md +9 -6
  14. package/agents/gsd-integration-checker.md +2 -0
  15. package/agents/gsd-nyquist-auditor.md +2 -0
  16. package/agents/gsd-phase-researcher.md +2 -0
  17. package/agents/gsd-plan-checker.md +2 -0
  18. package/agents/gsd-planner.md +2 -0
  19. package/agents/gsd-project-researcher.md +2 -0
  20. package/agents/gsd-research-synthesizer.md +2 -0
  21. package/agents/gsd-roadmapper.md +2 -0
  22. package/agents/gsd-security-auditor.md +2 -0
  23. package/agents/gsd-ui-auditor.md +2 -0
  24. package/agents/gsd-ui-checker.md +2 -0
  25. package/agents/gsd-ui-researcher.md +2 -0
  26. package/agents/gsd-verifier.md +5 -2
  27. package/bin/gsd-mcp-server.js +31 -0
  28. package/bin/install.js +411 -1146
  29. package/commands/gsd/review.md +6 -0
  30. package/gemini-extension.json +1 -1
  31. package/gsd-core/bin/gsd-tools.cjs +134 -8
  32. package/gsd-core/bin/lib/adapter-declarative.cjs +35 -0
  33. package/gsd-core/bin/lib/adapter-imperative.cjs +52 -0
  34. package/gsd-core/bin/lib/assumption-delta.cjs +231 -0
  35. package/gsd-core/bin/lib/capability-lifecycle.cjs +7 -7
  36. package/gsd-core/bin/lib/capability-loader.cjs +45 -9
  37. package/gsd-core/bin/lib/capability-lock.cjs +2 -2
  38. package/gsd-core/bin/lib/capability-registry.cjs +891 -82
  39. package/gsd-core/bin/lib/capability-source.cjs +26 -11
  40. package/gsd-core/bin/lib/capability-validator.cjs +222 -2
  41. package/gsd-core/bin/lib/cli-skew-check.cjs +44 -0
  42. package/gsd-core/bin/lib/command-aliases.cjs +8 -0
  43. package/gsd-core/bin/lib/commands.cjs +2 -1
  44. package/gsd-core/bin/lib/config.cjs +27 -0
  45. package/gsd-core/bin/lib/embedding-adapter.cjs +27 -0
  46. package/gsd-core/bin/lib/external-descriptor-trust.cjs +70 -0
  47. package/gsd-core/bin/lib/frontmatter.cjs +53 -6
  48. package/gsd-core/bin/lib/handshake-serialized.cjs +70 -0
  49. package/gsd-core/bin/lib/hook-bus.cjs +81 -0
  50. package/gsd-core/bin/lib/host-integration-sdk.cjs +53 -0
  51. package/gsd-core/bin/lib/host-integration.cjs +469 -0
  52. package/gsd-core/bin/lib/init.cjs +35 -7
  53. package/gsd-core/bin/lib/install-engine.cjs +755 -0
  54. package/gsd-core/bin/lib/install-profiles.cjs +35 -4
  55. package/gsd-core/bin/lib/installer-migrations.cjs +1 -1
  56. package/gsd-core/bin/lib/mcp-server.cjs +194 -0
  57. package/gsd-core/bin/lib/milestone.cjs +68 -40
  58. package/gsd-core/bin/lib/model-adapter.cjs +50 -0
  59. package/gsd-core/bin/lib/phase-id.cjs +18 -0
  60. package/gsd-core/bin/lib/phase.cjs +57 -90
  61. package/gsd-core/bin/lib/phases-command-router.cjs +4 -3
  62. package/gsd-core/bin/lib/planning-workspace.cjs +1 -1
  63. package/gsd-core/bin/lib/probe-core.cjs +132 -2
  64. package/gsd-core/bin/lib/review-reviewer-selection.cjs +129 -13
  65. package/gsd-core/bin/lib/roadmap-command-router.cjs +3 -2
  66. package/gsd-core/bin/lib/roadmap-parser.cjs +21 -11
  67. package/gsd-core/bin/lib/roadmap-upgrade.cjs +3 -2
  68. package/gsd-core/bin/lib/roadmap.cjs +33 -22
  69. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +65 -9
  70. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +54 -4
  71. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +5 -2
  72. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +1 -1
  73. package/gsd-core/bin/lib/runtime-name-policy.cjs +160 -30
  74. package/gsd-core/bin/lib/shell-command-projection.cjs +16 -0
  75. package/gsd-core/bin/lib/stale-bake-guard.cjs +254 -0
  76. package/gsd-core/bin/lib/state-command-router.cjs +4 -0
  77. package/gsd-core/bin/lib/state-io.cjs +55 -0
  78. package/gsd-core/bin/lib/state-transition.cjs +1603 -0
  79. package/gsd-core/bin/lib/state.cjs +327 -683
  80. package/gsd-core/bin/lib/surface.cjs +4 -1
  81. package/gsd-core/bin/lib/validate.cjs +2 -1
  82. package/gsd-core/bin/lib/verify.cjs +6 -4
  83. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +12 -2
  84. package/gsd-core/bin/lib/workstream-inventory.cjs +28 -0
  85. package/gsd-core/bin/lib/workstream.cjs +4 -4
  86. package/gsd-core/bin/shared/config-schema.manifest.json +9 -0
  87. package/gsd-core/references/agent-skills-bootstrap.md +60 -0
  88. package/gsd-core/references/honest-verifier.md +105 -0
  89. package/gsd-core/references/model-profiles.md +27 -0
  90. package/gsd-core/references/reviewer-instances.md +99 -0
  91. package/gsd-core/workflows/autonomous.md +30 -32
  92. package/gsd-core/workflows/complete-milestone.md +6 -10
  93. package/gsd-core/workflows/execute-phase.md +1 -1
  94. package/gsd-core/workflows/forensics.md +3 -3
  95. package/gsd-core/workflows/help/modes/full.md +1 -1
  96. package/gsd-core/workflows/manager.md +15 -15
  97. package/gsd-core/workflows/milestone-summary.md +3 -3
  98. package/gsd-core/workflows/new-milestone.md +6 -0
  99. package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +42 -0
  100. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +102 -0
  101. package/gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md +23 -0
  102. package/gsd-core/workflows/plan-phase.md +4 -159
  103. package/gsd-core/workflows/review.md +33 -2
  104. package/gsd-core/workflows/thread.md +4 -4
  105. package/gsd-core/workflows/verify-phase.md +11 -4
  106. package/gsd-core/workflows/verify-work.md +1 -2
  107. package/hooks/dist/gsd-graphify-update.sh +7 -1
  108. package/hooks/gsd-graphify-update.sh +7 -1
  109. package/package.json +6 -4
  110. package/scripts/ci-test-scope.cjs +38 -9
  111. package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -1
  112. package/scripts/lint-regression-test-names.allowlist.json +3 -0
  113. package/scripts/lint-test-file-count.allowlist.json +19 -5
  114. package/scripts/mutation-matrix.cjs +45 -3
  115. package/scripts/prompt-injection-scan.sh +8 -0
  116. package/scripts/run-tests.cjs +51 -1
  117. package/scripts/sync-manifest-versions.cjs +66 -14
  118. package/skills/gsd-review/SKILL.md +6 -0
  119. package/scripts/lint-windows-test-portability.cjs +0 -178
@@ -91,46 +91,7 @@ Parse JSON for: `researcher_model`, `planner_model`, `checker_model`, `research_
91
91
 
92
92
  ## 1.5. Closed-Phase Gate (#3569)
93
93
 
94
- The init JSON includes `phase_status` — one of `Pending | Planned | In Progress | Executed | Complete | Needs Review`. `Complete` means the phase has all summaries AND a `VERIFICATION.md` with `status: passed`. Replanning a closed phase silently rewrites plan docs that no longer match the shipped code, so the workflow must hard-stop here unless the operator explicitly overrides.
95
-
96
- Parse `phase_status` from the init JSON, then:
97
-
98
- ```bash
99
- FORCE_REPLAN=false
100
- if [[ "$ARGUMENTS" =~ (^|[[:space:]])--force([[:space:]]|$) ]]; then
101
- FORCE_REPLAN=true
102
- fi
103
-
104
- if [ "${phase_status}" = "Complete" ]; then
105
- if [[ "$ARGUMENTS" =~ (^|[[:space:]])--reviews([[:space:]]|$) ]]; then
106
- # --reviews on a closed phase is never legitimate — concerns belong in a
107
- # new phase or issue against the closed phase's commits.
108
- cat <<EOF >&2
109
- Phase ${phase_number} (${phase_name}) is already CLOSED (VERIFICATION status: passed).
110
- /gsd:plan-phase --reviews cannot replan a closed phase. If the review surfaced
111
- real concerns, open a follow-up phase or file an issue against the closed
112
- phase's commits. There is no --force override for --reviews on a closed phase.
113
- EOF
114
- exit 1
115
- fi
116
- if [ "$FORCE_REPLAN" != "true" ]; then
117
- cat <<EOF >&2
118
- Phase ${phase_number} (${phase_name}) is already CLOSED (VERIFICATION status: passed).
119
- Replanning a closed phase will overwrite plan docs that no longer match the
120
- shipped code. If you intentionally want to replan over closed work, re-run
121
- with: /gsd:plan-phase ${phase_number} --force
122
-
123
- Otherwise, to view what shipped, see: ${verification_path}
124
- EOF
125
- exit 1
126
- fi
127
- # FORCE_REPLAN=true: continue, but emit a banner so the operator sees the
128
- # decision in the transcript and in any committed plan docs.
129
- echo "WARNING: Replanning CLOSED phase ${phase_number} under --force. Verify the closeout was wrong before committing new plan docs." >&2
130
- fi
131
- ```
132
-
133
- The gate fires only on `Complete`. `Executed` and `Needs Review` are not gated — those states mean planning was finished but verification did not pass, and replanning is a legitimate next step.
94
+ Read and execute `gsd-core/workflows/plan-phase/steps/closed-phase-gate.md` — it parses `phase_status` from the init JSON, sets `FORCE_REPLAN` from `$ARGUMENTS`, and hard-stops replanning a `Complete` phase: `--reviews` on a closed phase is never overridable (exit 1), and replanning otherwise requires `--force` (else exit 1, pointing at `${verification_path}`); under `--force` it continues but emits a WARNING banner. Only `Complete` is gated — `Executed` / `Needs Review` are legitimate replans.
134
95
 
135
96
  ## 2. Parse and Normalize Arguments
136
97
 
@@ -249,103 +210,7 @@ MVP_MODE=$(gsd_run query phase.mvp-mode "${PHASE}" $MVP_FLAG_ARG --pick active)
249
210
 
250
211
  **If `--prd <filepath>` provided:**
251
212
 
252
- 1. Read the PRD file:
253
- ```bash
254
- PRD_CONTENT=$(cat "$PRD_FILE" 2>/dev/null)
255
- if [ -z "$PRD_CONTENT" ]; then
256
- echo "Error: PRD file not found: $PRD_FILE"
257
- exit 1
258
- fi
259
- ```
260
-
261
- 2. Display banner:
262
- ```
263
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
264
- GSD ► PRD EXPRESS PATH
265
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
266
-
267
- Using PRD: {PRD_FILE}
268
- Generating CONTEXT.md from requirements...
269
- ```
270
-
271
- 3. Parse the PRD content and generate CONTEXT.md. The orchestrator should:
272
- - Extract all requirements, user stories, acceptance criteria, and constraints from the PRD
273
- - Map each to a locked decision (everything in the PRD is treated as a locked decision)
274
- - Identify any areas the PRD doesn't cover and mark as "Claude's Discretion"
275
- - **Extract canonical refs** from ROADMAP.md for this phase, plus any specs/ADRs referenced in the PRD — expand to full file paths (MANDATORY)
276
- - Create CONTEXT.md in the phase directory
277
-
278
- 4. Write CONTEXT.md:
279
- ```markdown
280
- # Phase [X]: [Name] - Context
281
-
282
- **Gathered:** [date]
283
- **Status:** Ready for planning
284
- **Source:** PRD Express Path ({PRD_FILE})
285
-
286
- <domain>
287
- ## Phase Boundary
288
-
289
- [Extracted from PRD — what this phase delivers]
290
-
291
- </domain>
292
-
293
- <decisions>
294
- ## Implementation Decisions
295
-
296
- {For each requirement/story/criterion in the PRD:}
297
- ### [Category derived from content]
298
- - [Requirement as locked decision]
299
-
300
- ### Claude's Discretion
301
- [Areas not covered by PRD — implementation details, technical choices]
302
-
303
- </decisions>
304
-
305
- <canonical_refs>
306
- ## Canonical References
307
-
308
- **Downstream agents MUST read these before planning or implementing.**
309
-
310
- [MANDATORY. Extract from ROADMAP.md and any docs referenced in the PRD.
311
- Use full relative paths. Group by topic area.]
312
-
313
- ### [Topic area]
314
- - `path/to/spec-or-adr.md` — [What it decides/defines]
315
-
316
- [If no external specs: "No external specs — requirements fully captured in decisions above"]
317
-
318
- </canonical_refs>
319
-
320
- <specifics>
321
- ## Specific Ideas
322
-
323
- [Any specific references, examples, or concrete requirements from PRD]
324
-
325
- </specifics>
326
-
327
- <deferred>
328
- ## Deferred Ideas
329
-
330
- [Items in PRD explicitly marked as future/v2/out-of-scope]
331
- [If none: "None — PRD covers phase scope"]
332
-
333
- </deferred>
334
-
335
- ---
336
-
337
- *Phase: XX-name*
338
- *Context gathered: [date] via PRD Express Path*
339
- ```
340
-
341
- 5. Commit:
342
- ```bash
343
- gsd_run query commit "docs(${padded_phase}): generate context from PRD" --files "${phase_dir}/${padded_phase}-CONTEXT.md"
344
- ```
345
-
346
- 6. Set `context_content` to the generated CONTEXT.md content and continue to step 5 (Handle Research).
347
-
348
- **Effect:** This completely bypasses step 4 (Load CONTEXT.md) since we just created it. The rest of the workflow (research, planning, verification) proceeds normally with the PRD-derived context.
213
+ Read and execute `gsd-core/workflows/plan-phase/steps/prd-express-path.md` — it reads the PRD (`$PRD_FILE`), generates `CONTEXT.md` (every PRD requirement/story/criterion → locked decision, uncovered areas → "Claude's Discretion", canonical refs extracted from ROADMAP.md + PRD-referenced specs), commits it, sets `context_content`, and bypasses step 4 (Load CONTEXT.md). The rest of the workflow proceeds normally with the PRD-derived context.
349
214
 
350
215
  ## 3.6. Handle ADR Ingest Express Path
351
216
 
@@ -912,7 +777,7 @@ Output consumed by /gsd:execute-phase. Plans need:
912
777
  - Tasks in XML format with read_first and acceptance_criteria fields (MANDATORY on every task)
913
778
  - Verification criteria
914
779
  - must_haves for goal-backward verification
915
- - If the SPEC has an `## Edge Coverage` section, lift every `covered` edge's acceptance criterion into `must_haves.truths`, and every `backstop` edge into `must_haves.truths` as a non-inferable check (note it needs a held-out/property-based test). `unresolved` edges are explicit assumptions — surface them in the plan, do not silently drop them.
780
+ - If the SPEC has an `## Edge Coverage` section, lift every `covered` edge's acceptance criterion into `must_haves.truths` as a plain string, and every `backstop` edge **as a structured flat-scalar marker** — an object item `{ statement: <the check>, verification: backstop }`, NOT a prose note (the verifier branches deterministically on the `verification: backstop` field; a parenthetical is unparseable — the #1110 fragility). Use a flat scalar `verification:` continuation key, never a nested object (ADR-550 #1278). At verify time a `backstop` truth the verifier cannot confirm with explicit evidence abstains → `human_needed` (reason `insufficient_spec`), never a silent pass (#1154; see `references/honest-verifier.md`). `unresolved` edges are explicit assumptions — surface them in the plan, do not silently drop them.
916
781
  - If the SPEC has a `## Prohibitions` section, lift every resolved prohibition into the `must_haves.prohibitions:` sibling block (NOT `truths` — ADR-550 D3) carrying `statement` + `status` + `verification`; unresolved prohibitions are explicit assumptions — surface them in the plan, do not silently drop them. A prohibition is a must-NOT (negative) check that belongs in its own `must_haves.prohibitions` block. Never place a must-NOT under `must_haves.truths` — that block keeps positive-observable semantics only.
917
782
  - **"Artifacts this phase produces" section (MANDATORY)** — list every symbol this phase creates: decorators, classes, functions, CLI flags, struct/dataclass fields, new file paths. The plan-review-convergence source-grounding pass reads this section to exclude newly-created symbols from drift verification; omitting it causes new symbols to be flagged for acknowledgement.
918
783
  </downstream_consumer>
@@ -1730,27 +1595,7 @@ Verification: {Passed | Passed with override | Skipped}
1730
1595
  </offer_next>
1731
1596
 
1732
1597
  <windows_troubleshooting>
1733
- **Windows users:** If plan-phase freezes during agent spawning (common on Windows due to
1734
- stdio deadlocks with MCP servers — see Claude Code issue anthropics/claude-code#28126):
1735
-
1736
- 1. **Force-kill:** Close the terminal (Ctrl+C may not work)
1737
- 2. **Clean up orphaned processes:**
1738
- ```powershell
1739
- # Kill orphaned node processes from stale MCP servers
1740
- Get-Process node -ErrorAction SilentlyContinue | Where-Object {$_.StartTime -lt (Get-Date).AddHours(-1)} | Stop-Process -Force
1741
- ```
1742
- 3. **Clean up stale task directories:**
1743
- ```powershell
1744
- # Remove stale subagent task dirs (Claude Code never cleans these on crash)
1745
- Remove-Item -Recurse -Force "$env:USERPROFILE\.claude\tasks\*" -ErrorAction SilentlyContinue
1746
- ```
1747
- 4. **Reduce MCP server count:** Temporarily disable non-essential MCP servers in settings.json
1748
- 5. **Retry:** Restart Claude Code and run `/gsd:plan-phase` again
1749
-
1750
- If freezes persist, try `--skip-research` to reduce the agent chain from 3 to 2 agents:
1751
- ```
1752
- /gsd:plan-phase N --skip-research
1753
- ```
1598
+ Read `gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md` if plan-phase freezes on Windows during agent spawning (stdio deadlocks with MCP servers, anthropics/claude-code#28126) — it covers force-kill, orphaned-node cleanup, stale task-dir cleanup, reducing the MCP server count, and the `--skip-research` fallback.
1754
1599
  </windows_troubleshooting>
1755
1600
 
1756
1601
  <success_criteria>
@@ -66,6 +66,11 @@ Reviewer-selection precedence:
66
66
  - Known-but-undetected slugs emit an info note and are ignored
67
67
  - If all configured reviewers are unavailable, fail with an actionable message
68
68
 
69
+ **Reviewer instances (#1517, optional):** if `review.reviewer_instances` is configured,
70
+ instance names in `review.default_reviewers` run as independent identities. Resolution rules
71
+ are in `gsd-core/references/reviewer-instances.md` — load it lazily only when instances are
72
+ configured. Unconfigured → default path unchanged.
73
+
69
74
  If no CLIs are available:
70
75
  ```
71
76
  No external AI CLIs found. Install at least one:
@@ -243,6 +248,10 @@ else
243
248
  fi
244
249
  ```
245
250
 
251
+ **Reviewer instances (#1517, optional):** when instances are configured, each selected
252
+ instance invokes its base `cli` with its own `model`/`agent` (opaque argv, never
253
+ shell-interpolated). Exact invocation in `gsd-core/references/reviewer-instances.md`.
254
+
246
255
  For each selected CLI, invoke in sequence (not parallel — avoid rate limits):
247
256
 
248
257
  **Gemini:**
@@ -268,10 +277,15 @@ fi
268
277
  # $CODEX_BYPASS_FLAG is capability-gated above (#1115). Capture stderr to a .err
269
278
  # file (not /dev/null) so a non-zero exit — e.g. a flag the installed codex-cli
270
279
  # does not support — is diagnosable instead of a silent empty review.
280
+ # Capture the review via codex's own `-o/--output-last-message <FILE>` (only the
281
+ # final agent message) and discard stdout (#1698): on some platforms (Windows)
282
+ # codex writes process-teardown output to stdout *after* the final message, and a
283
+ # stdout redirect would append that noise to a non-empty file — slipping past the
284
+ # `[ ! -s … ]` empty-output guard as a silently polluted review.
271
285
  if [ -n "$CODEX_MODEL" ] && [ "$CODEX_MODEL" != "null" ]; then
272
- cat /tmp/gsd-review-prompt-{phase}.md | codex exec --ephemeral $CODEX_BYPASS_FLAG --model "$CODEX_MODEL" --skip-git-repo-check - 2>/tmp/gsd-review-codex-{phase}.err > /tmp/gsd-review-codex-{phase}.md
286
+ cat /tmp/gsd-review-prompt-{phase}.md | codex exec --ephemeral $CODEX_BYPASS_FLAG --model "$CODEX_MODEL" --skip-git-repo-check -o /tmp/gsd-review-codex-{phase}.md - 2>/tmp/gsd-review-codex-{phase}.err >/dev/null
273
287
  else
274
- cat /tmp/gsd-review-prompt-{phase}.md | codex exec --ephemeral $CODEX_BYPASS_FLAG --skip-git-repo-check - 2>/tmp/gsd-review-codex-{phase}.err > /tmp/gsd-review-codex-{phase}.md
288
+ cat /tmp/gsd-review-prompt-{phase}.md | codex exec --ephemeral $CODEX_BYPASS_FLAG --skip-git-repo-check -o /tmp/gsd-review-codex-{phase}.md - 2>/tmp/gsd-review-codex-{phase}.err >/dev/null
275
289
  fi
276
290
  if [ ! -s /tmp/gsd-review-codex-{phase}.md ]; then
277
291
  echo "Codex review failed or returned empty output. stderr:" > /tmp/gsd-review-codex-{phase}.md
@@ -634,6 +648,11 @@ Combine all review responses into `{phase_dir}/{padded_phase}-REVIEWS.md`:
634
648
 
635
649
  After all reviewers complete, collect trim metadata files written during the run. For each reviewer that was trimmed (i.e. a `.metadata.json` file exists and `hardFailed` or `omitted` is non-empty, or `projectMdShrunk` is true, or `planTruncationPct > 0`), include a `trimmed_reviewers` block in the frontmatter. Omit the key entirely if no reviewer was trimmed.
636
650
 
651
+ **Reviewer instances (#1517, optional):** when instances ran, frontmatter records their
652
+ names, each gets its own `## <Adapter> Review (<instance>)` section, and ≥2 same-cli
653
+ instances print a one-line shared-adapter caveat. Format in
654
+ `gsd-core/references/reviewer-instances.md`.
655
+
637
656
  ```markdown
638
657
  ---
639
658
  phase: {N}
@@ -684,6 +703,18 @@ trimmed_reviewers: # only present if at least one reviewer was trimmed
684
703
 
685
704
  ---
686
705
 
706
+ ## OpenCode Review (opencode-deepseek)
707
+
708
+ {opencode-deepseek instance review content — only present when this instance was selected}
709
+
710
+ ---
711
+
712
+ ## OpenCode Review (opencode-mimo)
713
+
714
+ {opencode-mimo instance review content — only present when this instance was selected}
715
+
716
+ ---
717
+
687
718
  ## Qwen Review
688
719
 
689
720
  {qwen review content}
@@ -68,8 +68,8 @@ When SUBCMD=close and SLUG is set (already sanitized):
68
68
 
69
69
  2. Update the thread file's frontmatter `status` field to `resolved` and `updated` to today's ISO date:
70
70
  ```bash
71
- gsd_run query frontmatter.set .planning/threads/{SLUG}.md status resolved
72
- gsd_run query frontmatter.set .planning/threads/{SLUG}.md updated YYYY-MM-DD
71
+ gsd_run query frontmatter.set .planning/threads/{SLUG}.md --field status --value resolved
72
+ gsd_run query frontmatter.set .planning/threads/{SLUG}.md --field updated --value YYYY-MM-DD
73
73
  ```
74
74
 
75
75
  3. Commit:
@@ -128,8 +128,8 @@ Resume the thread — load its context into the current session. Read the file c
128
128
 
129
129
  Update the thread's frontmatter `status` to `in_progress` if it was `open`:
130
130
  ```bash
131
- gsd_run query frontmatter.set .planning/threads/{SLUG}.md status in_progress
132
- gsd_run query frontmatter.set .planning/threads/{SLUG}.md updated YYYY-MM-DD
131
+ gsd_run query frontmatter.set .planning/threads/{SLUG}.md --field status --value in_progress
132
+ gsd_run query frontmatter.set .planning/threads/{SLUG}.md --field updated --value YYYY-MM-DD
133
133
  ```
134
134
 
135
135
  Thread content is displayed as plain text only — never executed or passed to agent prompts without DATA_START/DATA_END markers.
@@ -117,6 +117,8 @@ For each truth: identify supporting artifacts → check artifact status → chec
117
117
 
118
118
  **Behavior-dependent truths:** when a truth asserts a state transition or a cancellation/cleanup/ordering invariant, symbol presence + wiring is necessary but not sufficient — the code can be present and wired yet still leak state on the path the invariant covers. Mark such a truth ✓ VERIFIED only when a pre-existing test exercises the transition/invariant and passes (one named test, never the full suite); otherwise mark it ⚠️ PRESENT_BEHAVIOR_UNVERIFIED, emit a human-verification item, and exclude it from the verified score.
119
119
 
120
+ **Non-inferable (`backstop`) truths (#1154):** a `must_haves.truths` item in object form `{ statement, verification: backstop }` is non-inferable — the correct behavior is not derivable from the spec alone, so the verifier cannot self-detect the gap and would false-pass it confidently. Branch on the `verification: backstop` field (read via `truthVerification()`, never prose): if confirmable with **explicit evidence** (a passing wired held-out/property test, or a directly-observed behavior) → ✓ VERIFIED; otherwise **abstain** — mark ⚠️ `insufficient_spec`, emit an `unverified — held-out test recommended` human-verification item, exclude from the verified score (routes to `human_needed`). Exogenous only (never a self-judged "abstain if unsure"); an inferable truth is never abstained. See `references/honest-verifier.md`.
121
+
120
122
  **Example:** Truth "User can see existing messages" depends on Chat.tsx (renders), /api/chat GET (provides), Message model (schema). If Chat.tsx is a stub or API returns hardcoded [] → FAILED. If all exist, are substantive, and connected → VERIFIED.
121
123
  </step>
122
124
 
@@ -488,17 +490,22 @@ Classify status using this decision tree IN ORDER (most restrictive first):
488
490
  - **judgment-tier, autonomous run** (non-authoritative LLM-judge verdict): emit the `unverified-prohibition — human review recommended` flag and classify → **human_needed** (autonomous completion reads "complete with N flagged prohibitions"; never a silent pass, never a hard halt).
489
491
  - **judgment-tier, interactive run**: route to the end-of-phase human checkpoint → **human_needed**.
490
492
 
491
- 3. IF the previous step produced ANY human verification items — this includes every ⚠️ PRESENT_BEHAVIOR_UNVERIFIED truth:
493
+ 2b. IF any `must_haves.truths` item carries the `verification: backstop` marker (#1154 — the verify-time truth-axis mirror of ADR-550 D4) AND the verifier cannot confirm it with **explicit evidence** (a wired held-out/property-based test that PASSES, or a directly-observed behavior — i.e. `dispositionForUnverifiableTruth()` returns `status: 'unverified'`, `flagged: true`, `reason: 'insufficient_spec'`):
494
+ - **abstain → human_needed**, NEVER `passed` and never silently graded green. Emit a prominent `unverified — held-out test recommended` flag carrying the distinguishable `reason: insufficient_spec` (so it is not conflated with ordinary manual-UAT `human_needed`).
495
+ - *Autonomous run:* record it and continue — completion reads "complete with N unverified non-inferable checks"; never a hard halt of an AFK run. *Interactive run:* route to the end-of-phase human checkpoint.
496
+ - **Exogenous only:** abstention fires SOLELY on the `backstop` tag, never a self-judged "abstain if unsure" (N17). An **inferable** truth is NEVER abstained (over-abstention guard); a `backstop` truth WITH a passing wired held-out test reaches **passed**. Reliable on capable tiers (`sonnet`+); the budget `haiku` tier degrades — see `references/honest-verifier.md`.
497
+
498
+ 3. IF the previous step produced ANY human verification items — this includes every ⚠️ PRESENT_BEHAVIOR_UNVERIFIED truth and every abstained `insufficient_spec` backstop truth:
492
499
  → **human_needed** (even if all other truths VERIFIED)
493
500
 
494
- 4. IF all checks pass AND no human verification items AND no flagged prohibitions:
501
+ 4. IF all checks pass AND no human verification items AND no flagged prohibitions AND no abstained (`insufficient_spec`) truths:
495
502
  → **passed**
496
503
 
497
- **passed is ONLY valid when no human verification items AND no flagged prohibitions exist.** A prohibition (must-NOT) can never be silently absorbed into a `passed` verdict — that is the core failure mode ADR-550 D4 forbids.
504
+ **passed is ONLY valid when no human verification items, no flagged prohibitions, AND no abstained `insufficient_spec` truths exist.** Neither a prohibition (must-NOT) nor an unconfirmable non-inferable truth can ever be silently absorbed into a `passed` verdict — that is the core failure mode ADR-550 D4 forbids (now closed on both the prohibition and truth axes).
498
505
 
499
506
  A ⚠️ PRESENT_BEHAVIOR_UNVERIFIED truth is never FAILED and never VERIFIED: it does not trigger gaps_found (the code is present and wired) and is not counted as verified (its runtime behavior was not exercised). It routes through the existing human_needed sink — no new overall status.
500
507
 
501
- **Score:** `verified_truths / total_truths` — `verified_truths` counts ✓ VERIFIED truths plus PASSED (override) truths; ⚠️ PRESENT_BEHAVIOR_UNVERIFIED truths are the only ones excluded, reported separately as the `behavior_unverified` count. A headline N/N therefore certifies behavioral evidence for every behavior-dependent truth, not merely symbol presence.
508
+ **Score:** `verified_truths / total_truths` — `verified_truths` counts ✓ VERIFIED truths plus PASSED (override) truths; excluded are ⚠️ PRESENT_BEHAVIOR_UNVERIFIED truths (the `behavior_unverified` count) and abstained ⚠️ `insufficient_spec` backstop truths (#1154) — both are not ✓ VERIFIED and both route to `human_needed`. A headline N/N therefore certifies behavioral evidence for every behavior-dependent truth and explicit evidence for every non-inferable one, not merely symbol presence.
502
509
  </step>
503
510
 
504
511
  <step name="filter_deferred_items">
@@ -417,6 +417,7 @@ If no more tests → Go to `complete_session`
417
417
  Read the full UAT file.
418
418
 
419
419
  Find first test with `result: [pending]`.
420
+ If no `[pending]` test found → go to `complete_session`.
420
421
 
421
422
  Announce:
422
423
  ```
@@ -514,8 +515,6 @@ Resolve the security review failure before advancing to the next phase.
514
515
  All tests passed, but phase advancement is blocked until security review produces SECURITY.md.
515
516
 
516
517
  - `/gsd:secure-phase {phase}` — security review (required before advancing)
517
- - `/gsd:plan-phase {next}` — Plan next phase
518
- - `/gsd:execute-phase {next}` — Execute next phase
519
518
  - `/gsd:ui-review {phase}` — visual quality audit (if frontend files were modified)
520
519
  ```
521
520
 
@@ -45,7 +45,13 @@ process.stdin.on("end", () => {
45
45
  });
46
46
  ' 2>/dev/null || printf '\n')
47
47
  TOOL_NAME=$(printf '%s\n' "$TOOL_INFO" | sed -n '1p')
48
- COMMAND=$(printf '%s\n' "$TOOL_INFO" | sed -n '2p')
48
+ # Capture the FULL command (line 2 through EOF). Agent runtimes routinely emit
49
+ # HEAD-advancing commits as multi-line scripts (`cd /path` then `git add` then
50
+ # `git commit …`); reading only line 2 (`sed -n '2p'`) missed a `git commit`
51
+ # that was not on the first command line and silently no-op'd the rebuild
52
+ # (#1772). Line 2..EOF preserves embedded newlines; the `case` glob below
53
+ # matches the substring anywhere in the multi-line string.
54
+ COMMAND=$(printf '%s\n' "$TOOL_INFO" | sed -n '2,$p')
49
55
 
50
56
  [ "$TOOL_NAME" = "Bash" ] || exit 0
51
57
 
@@ -45,7 +45,13 @@ process.stdin.on("end", () => {
45
45
  });
46
46
  ' 2>/dev/null || printf '\n')
47
47
  TOOL_NAME=$(printf '%s\n' "$TOOL_INFO" | sed -n '1p')
48
- COMMAND=$(printf '%s\n' "$TOOL_INFO" | sed -n '2p')
48
+ # Capture the FULL command (line 2 through EOF). Agent runtimes routinely emit
49
+ # HEAD-advancing commits as multi-line scripts (`cd /path` then `git add` then
50
+ # `git commit …`); reading only line 2 (`sed -n '2p'`) missed a `git commit`
51
+ # that was not on the first command line and silently no-op'd the rebuild
52
+ # (#1772). Line 2..EOF preserves embedded newlines; the `case` glob below
53
+ # matches the substring anywhere in the multi-line string.
54
+ COMMAND=$(printf '%s\n' "$TOOL_INFO" | sed -n '2,$p')
49
55
 
50
56
  [ "$TOOL_NAME" = "Bash" ] || exit 0
51
57
 
package/package.json CHANGED
@@ -1,11 +1,13 @@
1
1
  {
2
2
  "name": "@opengsd/gsd-core",
3
- "version": "1.6.1",
3
+ "version": "1.7.0-rc.2",
4
4
  "description": "GSD Core is a meta-prompting, context engineering, and spec-driven development system for AI coding agents.",
5
+ "main": ".opencode/plugins/gsd-core.js",
5
6
  "bin": {
6
7
  "gsd-core": "bin/install.js",
7
8
  "gsd-tools": "gsd-core/bin/gsd-tools.cjs",
8
- "gsd_run": "gsd-core/bin/gsd_run"
9
+ "gsd_run": "gsd-core/bin/gsd_run",
10
+ "gsd-mcp-server": "bin/gsd-mcp-server.js"
9
11
  },
10
12
  "files": [
11
13
  "bin",
@@ -15,6 +17,7 @@
15
17
  "assets",
16
18
  "agents",
17
19
  ".claude-plugin",
20
+ ".opencode",
18
21
  "gemini-extension.json",
19
22
  "GEMINI.md",
20
23
  "hooks",
@@ -94,9 +97,8 @@
94
97
  "pretest:coverage": "npm run build:lib && npm run lint:skill-deps",
95
98
  "lint": "eslint . --cache --cache-location node_modules/.cache/eslint/",
96
99
  "lint:fix": "eslint . --fix",
97
- "lint:ci": "npm run lint && npm run lint:skill-deps && node scripts/lint-test-file-count.cjs && node scripts/lint-command-contract.cjs && node scripts/lint-pr-check-project-dir.cjs && npm run lint:legacy-name && node scripts/lint-regression-test-names.cjs && node scripts/lint-windows-test-portability.cjs && node scripts/lint-allow-test-rule-refs.cjs && node scripts/lint-resolution-provenance.cjs",
100
+ "lint:ci": "npm run lint && npm run lint:skill-deps && node scripts/lint-test-file-count.cjs && node scripts/lint-command-contract.cjs && node scripts/lint-pr-check-project-dir.cjs && npm run lint:legacy-name && node scripts/lint-regression-test-names.cjs && node scripts/lint-allow-test-rule-refs.cjs && node scripts/lint-resolution-provenance.cjs",
98
101
  "lint:allow-test-rule-refs": "node scripts/lint-allow-test-rule-refs.cjs",
99
- "lint:windows-test-portability": "node scripts/lint-windows-test-portability.cjs",
100
102
  "lint:regression-names": "node scripts/lint-regression-test-names.cjs",
101
103
  "lint:descriptions": "node scripts/lint-descriptions.cjs",
102
104
  "lint:skill-deps": "node scripts/lint-skill-deps.cjs",
@@ -118,6 +118,7 @@ const RULES = [
118
118
  tests: [
119
119
  'tests/semver-compare.test.cjs',
120
120
  'tests/bug-10-semver-policy-consolidation.test.cjs',
121
+ 'tests/golden-install-parity.test.cjs', // any src/installer change can alter emitted install artifacts → re-verify golden install parity (drift guard)
121
122
  ],
122
123
  },
123
124
  {
@@ -134,6 +135,7 @@ const RULES = [
134
135
  'tests/install-path-detection.test.cjs',
135
136
  'tests/release-tarball-smoke.install.test.cjs',
136
137
  'tests/runtime-artifact-layout.test.cjs',
138
+ 'tests/golden-install-parity.test.cjs', // any src/installer change can alter emitted install artifacts → re-verify golden install parity (drift guard)
137
139
  ],
138
140
  },
139
141
  {
@@ -212,17 +214,44 @@ const RULES = [
212
214
  ],
213
215
  },
214
216
  {
215
- name: 'configuration',
216
- match: path => ['config', 'configuration', 'model-catalog', 'model-profile'].some(k => path.includes(k)),
217
+ name: 'configuration',
218
+ match: path => ['config', 'configuration', 'model-catalog', 'model-profile'].some(k => path.includes(k)),
219
+ tests: [
220
+ 'tests/config.test.cjs',
221
+ 'tests/config-get-default.test.cjs',
222
+ 'tests/configuration-migrate-config.test.cjs',
223
+ 'tests/model-catalog-runtime-defaults.test.cjs',
224
+ 'tests/model-profiles.test.cjs',
225
+ ],
226
+ },
227
+ {
228
+ // ADR-1703 portability lint surface. Editing a rule, the shared vocab/guard
229
+ // helpers, or the eslint config that wires them must re-run the rule suites
230
+ // + the disable-ban. The disable-ban also scans bin/install.js and
231
+ // scripts/build-hooks.js (the Phase 6 glob-expansion surface), so changes
232
+ // to those files re-run it too.
233
+ name: 'portability lint rules (ADR-1703)',
234
+ match: path => path.startsWith('eslint-rules/') ||
235
+ path === 'eslint.config.mjs' ||
236
+ path === 'bin/install.js' ||
237
+ path === 'scripts/build-hooks.js',
217
238
  tests: [
218
- 'tests/config.test.cjs',
219
- 'tests/config-get-default.test.cjs',
220
- 'tests/configuration-migrate-config.test.cjs',
221
- 'tests/model-catalog-runtime-defaults.test.cjs',
222
- 'tests/model-profiles.test.cjs',
239
+ 'tests/portability-rule-disable-ban.test.cjs',
240
+ 'tests/portability-vocab-drift.test.cjs',
241
+ // All nine RuleTester suites (P1–P6) — editing any rule / the shared
242
+ // vocab+guard helpers / the eslint config re-runs the full rule family.
243
+ 'tests/no-path-literal-in-assert.rule.test.cjs',
244
+ 'tests/no-posix-mode-bit-assert.rule.test.cjs',
245
+ 'tests/no-unguarded-nonportable-exec.rule.test.cjs',
246
+ 'tests/no-crlf-fragile-split.rule.test.cjs',
247
+ 'tests/no-hardcoded-tmp.rule.test.cjs',
248
+ 'tests/no-bare-npm-exec.rule.test.cjs',
249
+ 'tests/require-userprofile-with-home.rule.test.cjs',
250
+ 'tests/normalize-path-in-content.rule.test.cjs',
251
+ 'tests/require-fs-op-fallback.rule.test.cjs',
223
252
  ],
224
253
  },
225
- ];
254
+ ];
226
255
 
227
256
  function usage() {
228
257
  return [
@@ -329,7 +358,7 @@ function classify(files) {
329
358
  // Determine if this file is product/pipeline code.
330
359
  // docs/ and root-level .md files are intentionally excluded.
331
360
  if (
332
- ['bin/', 'src/', 'gsd-core/', 'agents/', 'commands/', 'hooks/', 'tests/', 'scripts/'].some(p => file.startsWith(p)) ||
361
+ ['bin/', 'src/', 'gsd-core/', 'agents/', 'commands/', 'hooks/', 'tests/', 'scripts/', 'eslint-rules/'].some(p => file.startsWith(p)) ||
333
362
  file === 'package.json' || file === 'package-lock.json' ||
334
363
  (file.startsWith('tsconfig') && file.endsWith('.json')) ||
335
364
  file.startsWith('.github/rulesets/')
@@ -313,7 +313,6 @@
313
313
  "tests/verify-test-quality.test.cjs :: source-text-is-the-product",
314
314
  "tests/verify-work-auto-transition.test.cjs :: source-text-is-the-product",
315
315
  "tests/windows-robustness.test.cjs :: source-text-is-the-product",
316
- "tests/windows-test-parity-guard.test.cjs :: structural-regression-guard",
317
316
  "tests/workflow-compat.test.cjs :: source-text-is-the-product",
318
317
  "tests/workflow-guard-registration.test.cjs :: structural-regression-guard",
319
318
  "tests/workflow-maintainer-skip.test.cjs :: source-text-is-the-product",
@@ -5,10 +5,13 @@
5
5
  "bug-1367-claude-local-flat-command-layout.test.cjs",
6
6
  "bug-14-progress-auto-flag-dropped.test.cjs",
7
7
  "bug-167-query-meta-command.test.cjs",
8
+ "bug-1695-state-patch-clobbers-phase-name.test.cjs",
8
9
  "bug-17-askuserquestion-option-cap.test.cjs",
9
10
  "bug-170-workflow-fallback-install-hint.test.cjs",
10
11
  "bug-1736-local-install-commands.test.cjs",
11
12
  "bug-1754-js-hook-guard.test.cjs",
13
+ "bug-1760-state-prune-noop-template-field.test.cjs",
14
+ "bug-1761-state-sync-wrong-progress.test.cjs",
12
15
  "bug-1817-sh-hook-guard.test.cjs",
13
16
  "bug-1818-unknown-flags.test.cjs",
14
17
  "bug-1826-phases-clear-confirm.test.cjs",
@@ -99,6 +99,9 @@
99
99
  },
100
100
  "state": {
101
101
  "files": [
102
+ "bug-1695-state-patch-clobbers-phase-name.test.cjs",
103
+ "bug-1760-state-prune-noop-template-field.test.cjs",
104
+ "bug-1761-state-sync-wrong-progress.test.cjs",
102
105
  "bug-21-state-md-template-frontmatter.test.cjs",
103
106
  "bug-2630-state-frontmatter-milestone-switch.test.cjs",
104
107
  "bug-3127-state-begin-phase-idempotent.test.cjs",
@@ -107,10 +110,12 @@
107
110
  "bug-3454-state-dollar-backreference-growth.test.cjs",
108
111
  "bug-397-state-preserve-executor-authored.test.cjs",
109
112
  "bug-905-state-syncstatefrontmatter-preserve-scalars.test.cjs",
110
- "bug-948-state-noop-write-guard.test.cjs",
111
- "state-acquirestatelock-non-eexist.test.cjs",
112
- "state-prune.test.cjs",
113
- "state.test.cjs"
113
+ "bug-948-state-noop-write-guard.test.cjs",
114
+ "state-acquirestatelock-non-eexist.test.cjs",
115
+ "state-prune.test.cjs",
116
+ "state-rebuild-cli.test.cjs",
117
+ "state-rebuild.test.cjs",
118
+ "state.test.cjs"
114
119
  ],
115
120
  "issue": "180"
116
121
  },
@@ -142,9 +147,10 @@
142
147
  "install-regressions.test.cjs",
143
148
  "install-runtime-artifacts.test.cjs",
144
149
  "install-update-marker.test.cjs",
150
+ "install-write-confinement.test.cjs",
145
151
  "install.test.cjs"
146
152
  ],
147
- "issue": "TBD"
153
+ "issue": "1679"
148
154
  },
149
155
  "validate": {
150
156
  "files": [
@@ -184,6 +190,14 @@
184
190
  "fix-1464-docs-manifest-validation.test.cjs"
185
191
  ],
186
192
  "issue": "1496"
193
+ },
194
+ "host-integration": {
195
+ "files": [
196
+ "host-integration.test.cjs",
197
+ "host-integration-validator-parity.test.cjs",
198
+ "host-integration-descriptors.test.cjs"
199
+ ],
200
+ "issue": "1684"
187
201
  }
188
202
  }
189
203
  }
@@ -30,10 +30,52 @@
30
30
  */
31
31
 
32
32
  const { execFileSync } = require('child_process');
33
- const { readFileSync } = require('fs');
33
+ const fs = require('fs');
34
34
 
35
35
  const { ExitError, runMain } = require('./lib/cli-exit.cjs');
36
36
 
37
+ // ── Resilient stdin reader ────────────────────────────────────────────────────
38
+ // On macOS, libuv sets the stdin pipe fd to non-blocking mode. A synchronous
39
+ // readFileSync(process.stdin.fd) can therefore throw EAGAIN ("resource
40
+ // temporarily unavailable") when the writer hasn't yet filled the pipe — this
41
+ // is intermittent under heavy CI shard load and causes a spurious status 2
42
+ // exit. We work around it by calling fs.readSync in a loop and retrying on
43
+ // EAGAIN with a 1 ms synchronous pause (Atomics.wait on a fresh SharedArrayBuffer
44
+ // — no hot spin, no real-clock dependency, works under --experimental-vm-modules).
45
+ /**
46
+ * Read all of stdin synchronously, retrying on EAGAIN.
47
+ *
48
+ * @returns {string} UTF-8 decoded full stdin content.
49
+ */
50
+ function readStdinSync() {
51
+ const BUF_SIZE = 64 * 1024; // 64 KB chunks
52
+ const buf = Buffer.allocUnsafe(BUF_SIZE);
53
+ const chunks = [];
54
+
55
+ for (;;) {
56
+ let bytesRead;
57
+ try {
58
+ bytesRead = fs.readSync(process.stdin.fd, buf, 0, BUF_SIZE, null);
59
+ } catch (err) {
60
+ if (err.code === 'EAGAIN') {
61
+ // Non-blocking pipe not yet ready — yield for ~1 ms then retry.
62
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 1);
63
+ continue;
64
+ }
65
+ if (err.code === 'EOF') {
66
+ break;
67
+ }
68
+ throw err;
69
+ }
70
+ if (bytesRead === 0) {
71
+ break; // Clean EOF
72
+ }
73
+ chunks.push(Buffer.from(buf.slice(0, bytesRead)));
74
+ }
75
+
76
+ return Buffer.concat(chunks).toString('utf8');
77
+ }
78
+
37
79
  // ── Per-module mutation score ratchet ─────────────────────────────────────────
38
80
  // ADR-456 / issue #1187: every covered module declares a minScore floor.
39
81
  //
@@ -195,7 +237,7 @@ function resolveChangedFiles(args) {
195
237
  // When --base is absent AND stdin is not a TTY (isTTY is falsy / undefined),
196
238
  // read a newline-delimited file list from stdin.
197
239
  if (!args.base && process.stdin.isTTY !== true) {
198
- const raw = readFileSync(process.stdin.fd, 'utf8');
240
+ const raw = readStdinSync();
199
241
  return raw.split('\n').map(l => l.trim()).filter(Boolean);
200
242
  }
201
243
 
@@ -318,6 +360,6 @@ function resolveMutationBreak(raw) {
318
360
 
319
361
  // Export internals for programmatic use (tests/mutation-matrix-ratchet.test.cjs).
320
362
  // The require.main guard prevents main() from running when this file is require()d.
321
- module.exports = { COVERED, TARGET_MUTATION_SCORE, resolveMutationBreak };
363
+ module.exports = { COVERED, TARGET_MUTATION_SCORE, resolveMutationBreak, readStdinSync };
322
364
 
323
365
  if (require.main === module) runMain(main);