@opengsd/gsd-core 1.20.0 → 1.21.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 (154) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.opencode/plugins/gsd-core.js +33 -2
  4. package/agents/gsd-executor.md +1 -1
  5. package/agents/gsd-plan-checker.md +1 -1
  6. package/agents/gsd-planner.md +3 -3
  7. package/agents/gsd-verifier.md +2 -2
  8. package/bin/install.js +265 -161
  9. package/commands/gsd/autonomous.md +1 -1
  10. package/commands/gsd/new-milestone.md +2 -2
  11. package/commands/gsd/new-project.md +2 -2
  12. package/gsd-core/bin/gsd-tools.cjs +554 -823
  13. package/gsd-core/bin/lib/capability-activation.cjs +8 -2
  14. package/gsd-core/bin/lib/capability-command-router.cjs +6 -2
  15. package/gsd-core/bin/lib/capability-ledger.cjs +1 -1
  16. package/gsd-core/bin/lib/capability-lock.cjs +19 -27
  17. package/gsd-core/bin/lib/capability-registry.cjs +74 -71
  18. package/gsd-core/bin/lib/capability-validator.cjs +1 -0
  19. package/gsd-core/bin/lib/capability-writer.cjs +7 -4
  20. package/gsd-core/bin/lib/claude-orchestration-command-router.cjs +142 -62
  21. package/gsd-core/bin/lib/claude-orchestration.cjs +214 -122
  22. package/gsd-core/bin/lib/cli-surface.cjs +545 -0
  23. package/gsd-core/bin/lib/command-arg-projection.cjs +356 -103
  24. package/gsd-core/bin/lib/commands.cjs +99 -2
  25. package/gsd-core/bin/lib/config-file-read.cjs +85 -0
  26. package/gsd-core/bin/lib/config-loader.cjs +23 -15
  27. package/gsd-core/bin/lib/config.cjs +139 -101
  28. package/gsd-core/bin/lib/configuration.cjs +41 -2
  29. package/gsd-core/bin/lib/criterion-baseline.cjs +346 -0
  30. package/gsd-core/bin/lib/criterion-replay.cjs +380 -0
  31. package/gsd-core/bin/lib/estimate-cli.cjs +5 -1
  32. package/gsd-core/bin/lib/gap-checker.cjs +12 -1
  33. package/gsd-core/bin/lib/gate-evaluation-scope.cjs +593 -27
  34. package/gsd-core/bin/lib/gate-phase-context.cjs +16 -4
  35. package/gsd-core/bin/lib/graphify.cjs +19 -2
  36. package/gsd-core/bin/lib/gsd-tool-dir.cjs +134 -0
  37. package/gsd-core/bin/lib/health-diagnostic.cjs +5 -1
  38. package/gsd-core/bin/lib/init.cjs +20 -18
  39. package/gsd-core/bin/lib/install-effort-resolver.cjs +24 -4
  40. package/gsd-core/bin/lib/install-engine.cjs +20 -28
  41. package/gsd-core/bin/lib/install-fs-adapter.cjs +5 -1
  42. package/gsd-core/bin/lib/install-manifest-record.cjs +86 -0
  43. package/gsd-core/bin/lib/install-model-override-resolver.cjs +33 -3
  44. package/gsd-core/bin/lib/install-profiles.cjs +4 -2
  45. package/gsd-core/bin/lib/io.cjs +6 -0
  46. package/gsd-core/bin/lib/markdown-sectionizer.cjs +62 -31
  47. package/gsd-core/bin/lib/markdown-table.cjs +39 -1
  48. package/gsd-core/bin/lib/milestone.cjs +8 -6
  49. package/gsd-core/bin/lib/model-resolver.cjs +8 -4
  50. package/gsd-core/bin/lib/phase-entry-grammar.cjs +14 -0
  51. package/gsd-core/bin/lib/phase-id.cjs +26 -9
  52. package/gsd-core/bin/lib/phase-locator.cjs +8 -3
  53. package/gsd-core/bin/lib/phase.cjs +170 -45
  54. package/gsd-core/bin/lib/plan-dependency-graph.cjs +46 -23
  55. package/gsd-core/bin/lib/planning-snapshot.cjs +10 -6
  56. package/gsd-core/bin/lib/planning-workspace.cjs +20 -10
  57. package/gsd-core/bin/lib/profile-output.cjs +9 -6
  58. package/gsd-core/bin/lib/project-context.cjs +439 -0
  59. package/gsd-core/bin/lib/project-root.cjs +9 -230
  60. package/gsd-core/bin/lib/quick-batch-dispatch.cjs +1 -1
  61. package/gsd-core/bin/lib/report-parser.cjs +16 -0
  62. package/gsd-core/bin/lib/roadmap-parser.cjs +357 -50
  63. package/gsd-core/bin/lib/roadmap-upgrade.cjs +24 -5
  64. package/gsd-core/bin/lib/roadmap.cjs +2 -2
  65. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +317 -134
  66. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +81 -3
  67. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +6 -0
  68. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +279 -8
  69. package/gsd-core/bin/lib/runtime-slash.cjs +7 -2
  70. package/gsd-core/bin/lib/shell-command-projection.cjs +1010 -10
  71. package/gsd-core/bin/lib/stale-bake-guard.cjs +4 -1
  72. package/gsd-core/bin/lib/state-transition.cjs +103 -46
  73. package/gsd-core/bin/lib/state.cjs +448 -63
  74. package/gsd-core/bin/lib/surface.cjs +4 -7
  75. package/gsd-core/bin/lib/tdd-red-evidence.cjs +10 -3
  76. package/gsd-core/bin/lib/verify-command-router.cjs +6 -1
  77. package/gsd-core/bin/lib/verify.cjs +49 -6
  78. package/gsd-core/bin/lib/workstream-inventory.cjs +4 -4
  79. package/gsd-core/bin/lib/worktree-base-ref.cjs +32 -2
  80. package/gsd-core/bin/lib/worktree-safety.cjs +8 -111
  81. package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
  82. package/gsd-core/references/autonomous-smart-discuss.md +99 -18
  83. package/gsd-core/references/checkpoints.md +1 -1
  84. package/gsd-core/references/planner-guidance.md +11 -0
  85. package/gsd-core/references/planner-human-verify-mode.md +1 -1
  86. package/gsd-core/references/planner-reversibility.md +2 -0
  87. package/gsd-core/references/planner-verify-command-grounding.md +22 -6
  88. package/gsd-core/workflows/autonomous.md +4 -4
  89. package/gsd-core/workflows/code-review-fix.md +5 -7
  90. package/gsd-core/workflows/execute-phase.md +21 -7
  91. package/gsd-core/workflows/plan-phase/steps/plan-pre-dispatch.md +101 -0
  92. package/gsd-core/workflows/plan-phase.md +18 -19
  93. package/hooks/dist/gsd-agent-isolation-guard.js +4 -3
  94. package/hooks/dist/gsd-config-reload.js +2 -2
  95. package/hooks/dist/gsd-context-monitor.js +2 -1
  96. package/hooks/dist/gsd-cursor-subagent-start.js +9 -6
  97. package/hooks/dist/gsd-read-injection-scanner.js +2 -2
  98. package/hooks/dist/gsd-secret-read-guard.js +141 -6
  99. package/hooks/dist/gsd-statusline.js +3 -2
  100. package/hooks/dist/gsd-workflow-guard.js +2 -1
  101. package/hooks/dist/lib/config-read.js +49 -0
  102. package/hooks/dist/lib/git-cmd.js +3 -1
  103. package/hooks/dist/lib/git-head-tree.js +474 -0
  104. package/hooks/dist/lib/isolation-sentinel.js +168 -13
  105. package/hooks/dist/lib/secret-read-exemptions.js +161 -0
  106. package/hooks/gsd-agent-isolation-guard.js +4 -3
  107. package/hooks/gsd-config-reload.js +2 -2
  108. package/hooks/gsd-context-monitor.js +2 -1
  109. package/hooks/gsd-cursor-subagent-start.js +9 -6
  110. package/hooks/gsd-read-injection-scanner.js +2 -2
  111. package/hooks/gsd-secret-read-guard.js +141 -6
  112. package/hooks/gsd-statusline.js +3 -2
  113. package/hooks/gsd-workflow-guard.js +2 -1
  114. package/hooks/lib/config-read.js +49 -0
  115. package/hooks/lib/git-cmd.js +3 -1
  116. package/hooks/lib/git-head-tree.js +474 -0
  117. package/hooks/lib/isolation-sentinel.js +168 -13
  118. package/hooks/lib/secret-read-exemptions.js +161 -0
  119. package/package.json +7 -2
  120. package/pi/package-agents/gsd-executor.md +1 -1
  121. package/pi/package-agents/gsd-plan-checker.md +1 -1
  122. package/pi/package-agents/gsd-planner.md +3 -3
  123. package/pi/package-agents/gsd-verifier.md +2 -2
  124. package/pi/package-agents/manifest.json +4 -4
  125. package/pi/package-skills/gsd-autonomous/SKILL.md +1 -1
  126. package/pi/package-skills/gsd-new-milestone/SKILL.md +1 -1
  127. package/pi/package-skills/gsd-new-project/SKILL.md +1 -1
  128. package/scripts/changeset/cli.cjs +2 -2
  129. package/scripts/changeset/lint.cjs +28 -3
  130. package/scripts/changeset/serialize.cjs +12 -2
  131. package/scripts/ci-compact-v8-coverage.cjs +204 -0
  132. package/scripts/ci-fix-next-rerun.cjs +149 -0
  133. package/scripts/ci-next-health.cjs +52 -9
  134. package/scripts/ci-test-scope.cjs +77 -71
  135. package/scripts/command-contract-helpers.cjs +56 -7
  136. package/scripts/docs-guard-registry.cjs +3 -0
  137. package/scripts/gen-cli-surface-manifest.cjs +287 -0
  138. package/scripts/gen-platform-conformance-tier.cjs +258 -19
  139. package/scripts/lib/macos-conformance-tier.generated.cjs +59 -47
  140. package/scripts/lib/platform-conformance-tier.generated.cjs +23 -64
  141. package/scripts/lib/shellcheck-fetch.cjs +71 -4
  142. package/scripts/lint-removed-but-needed.cjs +562 -50
  143. package/scripts/merge-commit-pr.cjs +186 -0
  144. package/scripts/release-ancestry.cjs +86 -0
  145. package/scripts/release-notes/changelog-release-notes.cjs +345 -0
  146. package/scripts/release-notes/discord-release-summary.cjs +82 -1
  147. package/scripts/release-pipeline-pr.cjs +92 -0
  148. package/scripts/require-issue-link-policy.cjs +18 -9
  149. package/scripts/sync-next-version.cjs +11 -2
  150. package/scripts/timeout-evidence-from-ci.cjs +964 -0
  151. package/skills/gsd-autonomous/SKILL.md +1 -1
  152. package/skills/gsd-new-milestone/SKILL.md +1 -1
  153. package/skills/gsd-new-project/SKILL.md +1 -1
  154. package/vscode/package.json +1 -1
@@ -23,8 +23,7 @@ exports.findProjectRoot = findProjectRoot;
23
23
  exports.consentProjectRoot = consentProjectRoot;
24
24
  const node_fs_1 = __importDefault(require("node:fs"));
25
25
  const node_path_1 = __importDefault(require("node:path"));
26
- const node_os_1 = __importDefault(require("node:os"));
27
- const FIND_PROJECT_ROOT_MAX_DEPTH = 10;
26
+ const project_context_cjs_1 = require("./project-context.cjs");
28
27
  // #4894: an operator-supplied `--project-dir` IS the project root — the
29
28
  // dispatcher validates it and skips the ancestor walk-up for `cwd`, but code
30
29
  // that derives a root from a PHASE DIRECTORY (verification) never sees `cwd`.
@@ -39,235 +38,15 @@ function setExplicitProjectRoot(root) {
39
38
  function resolveProjectRoot(startDir) {
40
39
  return explicitProjectRoot ?? findProjectRoot(startDir);
41
40
  }
41
+ /**
42
+ * #6043 (ADR-4908): the ancestor walk moved verbatim to `project-context.cts`
43
+ * (`walkUpForPlanning`), which the resolver also owns. This stays a one-line
44
+ * delegate for its remaining callers (init, state, consentProjectRoot, the
45
+ * gsd-tools skew check, hooks/lib/isolation-sentinel.js); Phase 2 migrates them.
46
+ * It never spawns git.
47
+ */
42
48
  function findProjectRoot(startDir) {
43
- let resolvedStart;
44
- try {
45
- resolvedStart = node_path_1.default.resolve(startDir);
46
- }
47
- catch {
48
- return startDir;
49
- }
50
- const fsRoot = node_path_1.default.parse(resolvedStart).root;
51
- const home = node_os_1.default.homedir();
52
- // If startDir already contains .planning/, it IS the project root.
53
- try {
54
- const ownPlanningDir = resolvedStart + node_path_1.default.sep + '.planning';
55
- if (node_fs_1.default.existsSync(ownPlanningDir) && node_fs_1.default.statSync(ownPlanningDir).isDirectory()) {
56
- return startDir;
57
- }
58
- }
59
- catch {
60
- // fall through
61
- }
62
- // A `.planning` that is itself a symlink is a deliberate hop into a separate
63
- // tree (the documented "keep planning content out of the tracked repo"
64
- // convention: `.planning -> ~/.gsd-external-planning/<project>/`, its own git
65
- // repo). `fs.existsSync(d + '/.git')` DEREFERENCES `d`, so at `d ===
66
- // <the .planning symlink path>` it finds the EXTERNAL store's `.git`, and
67
- // both isInsideGitRepo/nearestGitRoot misread it as a crossed nested-child-
68
- // repo boundary (#2843's guard). `fs.lstatSync` does not dereference its
69
- // final path component, so it answers "is `d` ITSELF a symlink" (#4815).
70
- //
71
- // Deliberately scoped to a symlink named `.planning`, NOT any symlinked
72
- // directory: a nested child repo reached through some other symlinked
73
- // ancestor is still a real repo boundary, and skipping it would reopen
74
- // #2843 through a different trigger.
75
- function isSymlinkedPlanningDir(d) {
76
- if (node_path_1.default.basename(d) !== '.planning')
77
- return false;
78
- try {
79
- return node_fs_1.default.lstatSync(d).isSymbolicLink();
80
- }
81
- catch {
82
- return false;
83
- }
84
- }
85
- // Walk upward, mirroring isInsideGitRepo from the CJS reference.
86
- function isInsideGitRepo(candidateParent) {
87
- let d = resolvedStart;
88
- while (d !== fsRoot) {
89
- try {
90
- if (!isSymlinkedPlanningDir(d) && node_fs_1.default.existsSync(d + node_path_1.default.sep + '.git'))
91
- return true;
92
- }
93
- catch {
94
- // ignore
95
- }
96
- if (d === candidateParent)
97
- break;
98
- const next = node_path_1.default.dirname(d);
99
- if (next === d)
100
- break;
101
- d = next;
102
- }
103
- return false;
104
- }
105
- // #2843: nearest ancestor (including `from` itself) that contains a `.git`,
106
- // bounded by `upTo` (exclusive). Returns the git-repo root, or null if none
107
- // exists before `upTo` / the filesystem root. Used to detect a NESTED child
108
- // repo whose root is strictly below a candidate ancestor `.planning/` — in
109
- // that case the caller's repo boundary sits between start and the ancestor,
110
- // so trusting the ancestor's `.planning/` would silently cross into a
111
- // different project. (No `git` subprocess — fs walk only, matching
112
- // isInsideGitRepo's deliberate no-spawn contract.)
113
- function nearestGitRoot(from, upTo) {
114
- let d = from;
115
- while (d !== fsRoot) {
116
- if (d === upTo)
117
- break;
118
- try {
119
- if (!isSymlinkedPlanningDir(d) && node_fs_1.default.existsSync(d + node_path_1.default.sep + '.git'))
120
- return d;
121
- }
122
- catch {
123
- // ignore
124
- }
125
- const next = node_path_1.default.dirname(d);
126
- if (next === d)
127
- break;
128
- d = next;
129
- }
130
- return null;
131
- }
132
- let dir = resolvedStart;
133
- let depth = 0;
134
- while (dir !== fsRoot && depth < FIND_PROJECT_ROOT_MAX_DEPTH) {
135
- const parent = node_path_1.default.dirname(dir);
136
- if (parent === dir)
137
- break;
138
- if (parent === home)
139
- break;
140
- const parentPlanning = parent + node_path_1.default.sep + '.planning';
141
- let parentPlanningIsDir = false;
142
- try {
143
- parentPlanningIsDir = node_fs_1.default.existsSync(parentPlanning) && node_fs_1.default.statSync(parentPlanning).isDirectory();
144
- }
145
- catch {
146
- parentPlanningIsDir = false;
147
- }
148
- if (parentPlanningIsDir) {
149
- const configPath = parentPlanning + node_path_1.default.sep + 'config.json';
150
- let matched = false;
151
- try {
152
- const raw = node_fs_1.default.readFileSync(configPath, 'utf-8');
153
- const config = JSON.parse(raw);
154
- if (config && typeof config === 'object') {
155
- const cfg = config;
156
- const subReposValue = cfg['sub_repos'] ??
157
- (cfg['planning'] && typeof cfg['planning'] === 'object'
158
- ? cfg['planning']['sub_repos']
159
- : undefined);
160
- const subRepos = Array.isArray(subReposValue) ? subReposValue : [];
161
- if (subRepos.length > 0) {
162
- const relPath = node_path_1.default.relative(parent, resolvedStart);
163
- const topSegment = relPath.split(node_path_1.default.sep)[0];
164
- if (subRepos.includes(topSegment)) {
165
- return parent;
166
- }
167
- }
168
- if (cfg['multiRepo'] === true && isInsideGitRepo(parent)) {
169
- matched = true;
170
- }
171
- }
172
- }
173
- catch {
174
- // config.json missing or unparseable — fall through to .git heuristic.
175
- }
176
- if (matched)
177
- return parent;
178
- // Heuristic (3): parent has .planning/ and we're inside a git repo.
179
- // Before returning, check if any further ancestor has sub_repos that explicitly
180
- // claims our startDir — explicit sub_repos config takes precedence over the
181
- // implicit .git signal. (#1422)
182
- if (isInsideGitRepo(parent)) {
183
- // #2843: do NOT cross a git-repo boundary. If the caller is inside its
184
- // OWN nested repo whose root is strictly below `parent`, trusting
185
- // `parent`'s .planning/ would silently resolve to a DIFFERENT project.
186
- // isInsideGitRepo only proved SOME .git exists between start and parent;
187
- // verify that .git is parent's own (or absent between), not a nested
188
- // child repo. If nearestGitRoot finds a .git strictly below parent,
189
- // the boundary is crossed — fall through (do not return parent).
190
- if (nearestGitRoot(resolvedStart, parent) !== null) {
191
- dir = parent;
192
- depth += 1;
193
- continue;
194
- }
195
- // Lookahead: walk ancestors above `parent` to find a sub_repos claim.
196
- let ancestor = node_path_1.default.dirname(parent);
197
- let ancestorDepth = 0;
198
- while (ancestor !== fsRoot && ancestor !== home && ancestorDepth < FIND_PROJECT_ROOT_MAX_DEPTH) {
199
- const ancestorPlanning = ancestor + node_path_1.default.sep + '.planning';
200
- try {
201
- if (node_fs_1.default.existsSync(ancestorPlanning) && node_fs_1.default.statSync(ancestorPlanning).isDirectory()) {
202
- const ancestorConfig = ancestor + node_path_1.default.sep + '.planning' + node_path_1.default.sep + 'config.json';
203
- const rawA = node_fs_1.default.readFileSync(ancestorConfig, 'utf-8');
204
- const cfgA = JSON.parse(rawA);
205
- const subReposValueA = cfgA['sub_repos'] ??
206
- (cfgA['planning'] && typeof cfgA['planning'] === 'object'
207
- ? cfgA['planning']['sub_repos']
208
- : undefined);
209
- const subReposA = Array.isArray(subReposValueA) ? subReposValueA : [];
210
- if (subReposA.length > 0) {
211
- const relPathA = node_path_1.default.relative(ancestor, resolvedStart);
212
- const topSegmentA = relPathA.split(node_path_1.default.sep)[0];
213
- if (subReposA.includes(topSegmentA)) {
214
- return ancestor;
215
- }
216
- }
217
- }
218
- }
219
- catch {
220
- // ignore — config missing or unparseable, keep walking
221
- }
222
- const nextAncestor = node_path_1.default.dirname(ancestor);
223
- if (nextAncestor === ancestor)
224
- break;
225
- ancestor = nextAncestor;
226
- ancestorDepth += 1;
227
- }
228
- return parent;
229
- }
230
- }
231
- dir = parent;
232
- depth += 1;
233
- }
234
- // Heuristic (4): nearest ancestor .planning/ — last resort before fallback.
235
- // Runs only after heuristics (1)–(3) have been exhausted without a match,
236
- // ensuring sub_repos / multiRepo / .git-based resolution always wins when
237
- // applicable. Walks upward again within the same FIND_PROJECT_ROOT_MAX_DEPTH
238
- // bound; returns the nearest ancestor directory that contains a .planning/
239
- // subdirectory so config resolves correctly when invoked from a plain
240
- // descendant of a single-repo project. (#1414)
241
- let dir2 = resolvedStart;
242
- let depth2 = 0;
243
- while (dir2 !== fsRoot && depth2 < FIND_PROJECT_ROOT_MAX_DEPTH) {
244
- const parent2 = node_path_1.default.dirname(dir2);
245
- if (parent2 === dir2)
246
- break;
247
- try {
248
- const candidatePlanning = parent2 + node_path_1.default.sep + '.planning';
249
- if (node_fs_1.default.existsSync(candidatePlanning) && node_fs_1.default.statSync(candidatePlanning).isDirectory()) {
250
- // #2843: do not cross a git-repo boundary. If the caller is inside its
251
- // own nested repo (a .git strictly below parent2), parent2's .planning/
252
- // belongs to a DIFFERENT project — keep walking is wrong; stop and fall
253
- // through to the startDir fallback instead of silently resolving to the
254
- // ancestor project. (Reached only when no .git exists anywhere in the
255
- // chain per the triage, but guard defensively.)
256
- if (nearestGitRoot(resolvedStart, parent2) !== null) {
257
- break;
258
- }
259
- return parent2;
260
- }
261
- }
262
- catch {
263
- // ignore fs errors and continue walking
264
- }
265
- if (parent2 === home)
266
- break;
267
- dir2 = parent2;
268
- depth2 += 1;
269
- }
270
- return startDir;
49
+ return (0, project_context_cjs_1.walkUpForPlanning)(startDir).root;
271
50
  }
272
51
  /**
273
52
  * #1459 (IC-01 / CB-4): THE single canonical derivation of the PROJECT ROOT used to bind/lookup a
@@ -188,7 +188,7 @@ function routeMergeOutcome(outcome) {
188
188
  }
189
189
  /**
190
190
  * Crash-window duplicate-dispatch guard
191
- * (`.gsd/phase/feat-3677-quick-batch-hardening-acceptance/40-design.md` §1).
191
+ * (the #3677 design, PR #4240, §1).
192
192
  * `quick-batch resume`'s `eligible` is purely status/dependency-derived —
193
193
  * it has no awareness that an item already finished executing (a real
194
194
  * commit, `SUMMARY.md` written) before a coordinator crash left
@@ -9,6 +9,14 @@ exports.ReportFormat = {
9
9
  Tap: 'tap', Junit: 'junit', SwiftTesting: 'swift-testing', Unittest: 'unittest', GoTest: 'go-test', Unknown: 'unknown',
10
10
  };
11
11
  exports.TestStatus = { Passed: 'passed', Failed: 'failed', Skipped: 'skipped', Todo: 'todo' };
12
+ /**
13
+ * A name that is a JS/TS test source file, as opposed to a Java class or method. Limited to the
14
+ * extensions vitest, jest and node:test load: a wider list (`go`, `rs`, `py`, ...) would flag a
15
+ * Surefire class-level entry whose FQN ends in that segment (`com.acme.go`, #4724).
16
+ */
17
+ const TEST_FILE_NAME = /\.[cm]?[jt]sx?$/;
18
+ /** jest-junit's classNameTemplate for a suite that produced no results (utils/buildJsonResults.js). */
19
+ const JEST_SUITE_FAILED_TO_RUN = 'Test suite failed to run';
12
20
  function parseTap(output) {
13
21
  const report = { format: exports.ReportFormat.Tap, valid: true, tests: [], issues: [] };
14
22
  const parser = new tap_parser_cjs_1.Parser({ strict: true });
@@ -62,12 +70,18 @@ function parseTap(output) {
62
70
  skipped ||= Boolean(parent.closingTestPoint?.skip);
63
71
  todo ||= Boolean(parent.closingTestPoint?.todo);
64
72
  }
73
+ // node:test reports a file that exits non-zero without a failing subtest as `not ok N - <file>`
74
+ // with `error: 'test failed'` / `code: 'ERR_TEST_FAILURE'` (#5721): it asserted nothing.
75
+ const diag = result.diag;
76
+ const fileFailure = !result.ok && diag?.['error'] === 'test failed' && diag['code'] === 'ERR_TEST_FAILURE'
77
+ && TEST_FILE_NAME.test(result.name);
65
78
  report.tests.push({
66
79
  name: result.name,
67
80
  identities: [...new Set([result.name, result.fullname])],
68
81
  group: null,
69
82
  groupIdentities: [],
70
83
  status: skipped ? exports.TestStatus.Skipped : todo ? exports.TestStatus.Todo : result.ok ? exports.TestStatus.Passed : exports.TestStatus.Failed,
84
+ ...(fileFailure ? { loadFailure: true } : {}),
71
85
  });
72
86
  }
73
87
  if (!completed)
@@ -111,6 +125,8 @@ function parseJunit(output) {
111
125
  group: className || null,
112
126
  groupIdentities: className ? [className, className.split('.').at(-1)] : [],
113
127
  status: exports.TestStatus.Passed,
128
+ ...(className === JEST_SUITE_FAILED_TO_RUN || (name === className && TEST_FILE_NAME.test(name))
129
+ ? { loadFailure: true } : {}),
114
130
  };
115
131
  }
116
132
  else if (current && parent === 'testcase') {