@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.opencode/plugins/gsd-core.js +33 -2
- package/agents/gsd-executor.md +1 -1
- package/agents/gsd-plan-checker.md +1 -1
- package/agents/gsd-planner.md +3 -3
- package/agents/gsd-verifier.md +2 -2
- package/bin/install.js +265 -161
- package/commands/gsd/autonomous.md +1 -1
- package/commands/gsd/new-milestone.md +2 -2
- package/commands/gsd/new-project.md +2 -2
- package/gsd-core/bin/gsd-tools.cjs +554 -823
- package/gsd-core/bin/lib/capability-activation.cjs +8 -2
- package/gsd-core/bin/lib/capability-command-router.cjs +6 -2
- package/gsd-core/bin/lib/capability-ledger.cjs +1 -1
- package/gsd-core/bin/lib/capability-lock.cjs +19 -27
- package/gsd-core/bin/lib/capability-registry.cjs +74 -71
- package/gsd-core/bin/lib/capability-validator.cjs +1 -0
- package/gsd-core/bin/lib/capability-writer.cjs +7 -4
- package/gsd-core/bin/lib/claude-orchestration-command-router.cjs +142 -62
- package/gsd-core/bin/lib/claude-orchestration.cjs +214 -122
- package/gsd-core/bin/lib/cli-surface.cjs +545 -0
- package/gsd-core/bin/lib/command-arg-projection.cjs +356 -103
- package/gsd-core/bin/lib/commands.cjs +99 -2
- package/gsd-core/bin/lib/config-file-read.cjs +85 -0
- package/gsd-core/bin/lib/config-loader.cjs +23 -15
- package/gsd-core/bin/lib/config.cjs +139 -101
- package/gsd-core/bin/lib/configuration.cjs +41 -2
- package/gsd-core/bin/lib/criterion-baseline.cjs +346 -0
- package/gsd-core/bin/lib/criterion-replay.cjs +380 -0
- package/gsd-core/bin/lib/estimate-cli.cjs +5 -1
- package/gsd-core/bin/lib/gap-checker.cjs +12 -1
- package/gsd-core/bin/lib/gate-evaluation-scope.cjs +593 -27
- package/gsd-core/bin/lib/gate-phase-context.cjs +16 -4
- package/gsd-core/bin/lib/graphify.cjs +19 -2
- package/gsd-core/bin/lib/gsd-tool-dir.cjs +134 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +5 -1
- package/gsd-core/bin/lib/init.cjs +20 -18
- package/gsd-core/bin/lib/install-effort-resolver.cjs +24 -4
- package/gsd-core/bin/lib/install-engine.cjs +20 -28
- package/gsd-core/bin/lib/install-fs-adapter.cjs +5 -1
- package/gsd-core/bin/lib/install-manifest-record.cjs +86 -0
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +33 -3
- package/gsd-core/bin/lib/install-profiles.cjs +4 -2
- package/gsd-core/bin/lib/io.cjs +6 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +62 -31
- package/gsd-core/bin/lib/markdown-table.cjs +39 -1
- package/gsd-core/bin/lib/milestone.cjs +8 -6
- package/gsd-core/bin/lib/model-resolver.cjs +8 -4
- package/gsd-core/bin/lib/phase-entry-grammar.cjs +14 -0
- package/gsd-core/bin/lib/phase-id.cjs +26 -9
- package/gsd-core/bin/lib/phase-locator.cjs +8 -3
- package/gsd-core/bin/lib/phase.cjs +170 -45
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +46 -23
- package/gsd-core/bin/lib/planning-snapshot.cjs +10 -6
- package/gsd-core/bin/lib/planning-workspace.cjs +20 -10
- package/gsd-core/bin/lib/profile-output.cjs +9 -6
- package/gsd-core/bin/lib/project-context.cjs +439 -0
- package/gsd-core/bin/lib/project-root.cjs +9 -230
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +1 -1
- package/gsd-core/bin/lib/report-parser.cjs +16 -0
- package/gsd-core/bin/lib/roadmap-parser.cjs +357 -50
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +24 -5
- package/gsd-core/bin/lib/roadmap.cjs +2 -2
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +317 -134
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +81 -3
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +6 -0
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +279 -8
- package/gsd-core/bin/lib/runtime-slash.cjs +7 -2
- package/gsd-core/bin/lib/shell-command-projection.cjs +1010 -10
- package/gsd-core/bin/lib/stale-bake-guard.cjs +4 -1
- package/gsd-core/bin/lib/state-transition.cjs +103 -46
- package/gsd-core/bin/lib/state.cjs +448 -63
- package/gsd-core/bin/lib/surface.cjs +4 -7
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +10 -3
- package/gsd-core/bin/lib/verify-command-router.cjs +6 -1
- package/gsd-core/bin/lib/verify.cjs +49 -6
- package/gsd-core/bin/lib/workstream-inventory.cjs +4 -4
- package/gsd-core/bin/lib/worktree-base-ref.cjs +32 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +8 -111
- package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
- package/gsd-core/references/autonomous-smart-discuss.md +99 -18
- package/gsd-core/references/checkpoints.md +1 -1
- package/gsd-core/references/planner-guidance.md +11 -0
- package/gsd-core/references/planner-human-verify-mode.md +1 -1
- package/gsd-core/references/planner-reversibility.md +2 -0
- package/gsd-core/references/planner-verify-command-grounding.md +22 -6
- package/gsd-core/workflows/autonomous.md +4 -4
- package/gsd-core/workflows/code-review-fix.md +5 -7
- package/gsd-core/workflows/execute-phase.md +21 -7
- package/gsd-core/workflows/plan-phase/steps/plan-pre-dispatch.md +101 -0
- package/gsd-core/workflows/plan-phase.md +18 -19
- package/hooks/dist/gsd-agent-isolation-guard.js +4 -3
- package/hooks/dist/gsd-config-reload.js +2 -2
- package/hooks/dist/gsd-context-monitor.js +2 -1
- package/hooks/dist/gsd-cursor-subagent-start.js +9 -6
- package/hooks/dist/gsd-read-injection-scanner.js +2 -2
- package/hooks/dist/gsd-secret-read-guard.js +141 -6
- package/hooks/dist/gsd-statusline.js +3 -2
- package/hooks/dist/gsd-workflow-guard.js +2 -1
- package/hooks/dist/lib/config-read.js +49 -0
- package/hooks/dist/lib/git-cmd.js +3 -1
- package/hooks/dist/lib/git-head-tree.js +474 -0
- package/hooks/dist/lib/isolation-sentinel.js +168 -13
- package/hooks/dist/lib/secret-read-exemptions.js +161 -0
- package/hooks/gsd-agent-isolation-guard.js +4 -3
- package/hooks/gsd-config-reload.js +2 -2
- package/hooks/gsd-context-monitor.js +2 -1
- package/hooks/gsd-cursor-subagent-start.js +9 -6
- package/hooks/gsd-read-injection-scanner.js +2 -2
- package/hooks/gsd-secret-read-guard.js +141 -6
- package/hooks/gsd-statusline.js +3 -2
- package/hooks/gsd-workflow-guard.js +2 -1
- package/hooks/lib/config-read.js +49 -0
- package/hooks/lib/git-cmd.js +3 -1
- package/hooks/lib/git-head-tree.js +474 -0
- package/hooks/lib/isolation-sentinel.js +168 -13
- package/hooks/lib/secret-read-exemptions.js +161 -0
- package/package.json +7 -2
- package/pi/package-agents/gsd-executor.md +1 -1
- package/pi/package-agents/gsd-plan-checker.md +1 -1
- package/pi/package-agents/gsd-planner.md +3 -3
- package/pi/package-agents/gsd-verifier.md +2 -2
- package/pi/package-agents/manifest.json +4 -4
- package/pi/package-skills/gsd-autonomous/SKILL.md +1 -1
- package/pi/package-skills/gsd-new-milestone/SKILL.md +1 -1
- package/pi/package-skills/gsd-new-project/SKILL.md +1 -1
- package/scripts/changeset/cli.cjs +2 -2
- package/scripts/changeset/lint.cjs +28 -3
- package/scripts/changeset/serialize.cjs +12 -2
- package/scripts/ci-compact-v8-coverage.cjs +204 -0
- package/scripts/ci-fix-next-rerun.cjs +149 -0
- package/scripts/ci-next-health.cjs +52 -9
- package/scripts/ci-test-scope.cjs +77 -71
- package/scripts/command-contract-helpers.cjs +56 -7
- package/scripts/docs-guard-registry.cjs +3 -0
- package/scripts/gen-cli-surface-manifest.cjs +287 -0
- package/scripts/gen-platform-conformance-tier.cjs +258 -19
- package/scripts/lib/macos-conformance-tier.generated.cjs +59 -47
- package/scripts/lib/platform-conformance-tier.generated.cjs +23 -64
- package/scripts/lib/shellcheck-fetch.cjs +71 -4
- package/scripts/lint-removed-but-needed.cjs +562 -50
- package/scripts/merge-commit-pr.cjs +186 -0
- package/scripts/release-ancestry.cjs +86 -0
- package/scripts/release-notes/changelog-release-notes.cjs +345 -0
- package/scripts/release-notes/discord-release-summary.cjs +82 -1
- package/scripts/release-pipeline-pr.cjs +92 -0
- package/scripts/require-issue-link-policy.cjs +18 -9
- package/scripts/sync-next-version.cjs +11 -2
- package/scripts/timeout-evidence-from-ci.cjs +964 -0
- package/skills/gsd-autonomous/SKILL.md +1 -1
- package/skills/gsd-new-milestone/SKILL.md +1 -1
- package/skills/gsd-new-project/SKILL.md +1 -1
- 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
|
|
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
|
-
|
|
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
|
-
* (
|
|
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') {
|