test(#1975): consolidate 51 CLI + scripts-tooling regression tests into module suites
Fold 51 issue-named CLI black-box + scripts-tooling regression files into their canonical module suites (runtime-launcher-parity, worktree-safety, install-*, managed-hooks, read-guard, capability-registry, etc.), plus a NEW slash-command-namespace.test.cjs grouping the 4 slash/colon-namespace-leak invariant suites that had no canonical owner. Verbatim block-scoped describe wrappers; 427 subtests conserved 1:1. Host-env pre-check (per B2): no CLI-receiving host sets a redirecting GSD_WORKSTREAM/GSD_PROJECT value. One folded suite (bug-3668 runtime resolver) creates an extension-less PATH gsd-tools stub + bash -c; co-locating it with the host's chmodSync tripped local/no-unguarded-nonportable-exec, so it's now Windows-guarded (skip on win32) matching the host suite's own bash -c guard. Regenerates regression-name allowlist (222->182), ratchets file-count allowlist (graphify 7->6, docs entry removed), makes 26 relocated allow-test-rule exemptions issue-ref-compliant (ADR-456; prunes stale ids). Repoints 13 tests/ references across CONTEXT.md, COMMANDS.md/FEATURES.md (EN + ja/ko/pt/zh) and ADR-0002. lint:ci green. Part of epic #1969. Closes #1975. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -2271,3 +2271,199 @@ describe('#1912 — init.progress fails safe in workstream mode with no active w
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// roadmap analyze command
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────
|
||||
// Folded from tests/bug-3491-nested-git-worktree.test.cjs — consolidation epic #1969 (B6 #1975)
|
||||
// ────────────────────────────────────────────────────────────────────────
|
||||
{
|
||||
const { describe: __foldDescribe } = require('node:test');
|
||||
__foldDescribe("folded:bug-3491-nested-git-worktree (consolidation epic #1969 B6 #1975)", () => {
|
||||
// allow-test-rule: source-text-is-the-product (see #3491)
|
||||
// Bug #3491 — new-project workflow creates nested .git in subdirectory when
|
||||
// parent already has git repo.
|
||||
//
|
||||
// The workflow's `has_git` boolean was derived from `pathExists(cwd, '.git')`
|
||||
// — a shallow check that only sees a `.git` entry directly in the current
|
||||
// directory. Subdirectories of an existing git worktree therefore reported
|
||||
// `has_git: false`, causing the workflow's `git init` step to create a nested
|
||||
// `.git` inside the outer repo's worktree. Subsequent gsd-sdk commits then
|
||||
// targeted the nested repo instead of the outer one, silently dropping all
|
||||
// planning artefacts from the outer repo's history.
|
||||
//
|
||||
// This test asserts the corrected semantics, mirroring `git rev-parse
|
||||
// --is-inside-work-tree`:
|
||||
//
|
||||
// - `has_git: true` is reported whenever the cwd is inside a git worktree,
|
||||
// even when no `.git` entry is in cwd itself.
|
||||
// - The init payload surfaces `git_worktree_root` and `in_nested_subdir` so
|
||||
// the workflow can warn the user and skip `git init`.
|
||||
// - The workflow markdown's `git init` step is gated on
|
||||
// `in_nested_subdir: false`, never unconditional under `has_git: false`.
|
||||
|
||||
'use strict';
|
||||
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const { execSync } = require('node:child_process');
|
||||
|
||||
const { runGsdTools, cleanup } = require('./helpers.cjs');
|
||||
|
||||
const WORKFLOW_PATH = path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'gsd-core',
|
||||
'workflows',
|
||||
'new-project.md',
|
||||
);
|
||||
|
||||
// ─── Helper: create outer git repo with a nested workstream subdir ─────────
|
||||
|
||||
// On Windows the runtime emits forward slashes (git's convention) while
|
||||
// path.join produces backslashes — normalize both sides via the shared
|
||||
// toPosixPath helper before any equality comparison.
|
||||
const { toPosixPath: normalizePath } = require('./helpers.cjs');
|
||||
|
||||
function createOuterRepoWithSubdir(prefix = 'bug-3491-') {
|
||||
const outer = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
||||
// macOS /tmp -> /private/tmp; on Windows the runner's %TEMP% is the 8.3
|
||||
// short-name (RUNNER~1) and the runtime resolves to the long form.
|
||||
// realpathSync.native handles both; then normalize separators for compare.
|
||||
const outerReal = fs.realpathSync.native(outer);
|
||||
execSync('git init', { cwd: outerReal, stdio: 'pipe' });
|
||||
execSync('git config user.email "test@test.com"', { cwd: outerReal, stdio: 'pipe' });
|
||||
execSync('git config user.name "Test"', { cwd: outerReal, stdio: 'pipe' });
|
||||
execSync('git config commit.gpgsign false', { cwd: outerReal, stdio: 'pipe' });
|
||||
fs.writeFileSync(path.join(outerReal, 'README.md'), '# outer\n');
|
||||
execSync('git add -A', { cwd: outerReal, stdio: 'pipe' });
|
||||
execSync('git commit -m "initial"', { cwd: outerReal, stdio: 'pipe' });
|
||||
|
||||
const subdir = path.join(outerReal, 'workstreams', 'my-project');
|
||||
fs.mkdirSync(subdir, { recursive: true });
|
||||
return { outer: outerReal, subdir };
|
||||
}
|
||||
|
||||
// ─── Behavioural tests against the live `init new-project` handler ─────────
|
||||
|
||||
test('bug-3491: init new-project reports has_git: true inside parent git worktree', () => {
|
||||
const { outer, subdir } = createOuterRepoWithSubdir();
|
||||
try {
|
||||
const result = runGsdTools('init new-project', subdir);
|
||||
assert.ok(result.success, `init new-project failed: ${result.error}`);
|
||||
|
||||
const payload = JSON.parse(result.output);
|
||||
|
||||
// Core fix: shallow `.git in cwd` check was wrong — we are inside the
|
||||
// outer worktree, so the workflow MUST see has_git: true.
|
||||
assert.strictEqual(
|
||||
payload.has_git,
|
||||
true,
|
||||
'expected has_git=true when cwd is inside an existing git worktree (parent .git)',
|
||||
);
|
||||
|
||||
// The workflow needs the worktree root and a nesting flag to decide
|
||||
// whether to skip `git init` and emit a friendly warning.
|
||||
assert.strictEqual(
|
||||
normalizePath(payload.git_worktree_root),
|
||||
normalizePath(outer),
|
||||
`expected git_worktree_root to be the outer repo (${outer}), got: ${payload.git_worktree_root}`,
|
||||
);
|
||||
assert.strictEqual(
|
||||
payload.in_nested_subdir,
|
||||
true,
|
||||
'expected in_nested_subdir=true when cwd is a subdirectory of the worktree root',
|
||||
);
|
||||
} finally {
|
||||
cleanup(outer);
|
||||
}
|
||||
});
|
||||
|
||||
test('bug-3491: init new-project reports has_git: true at worktree root with in_nested_subdir: false', () => {
|
||||
const { outer } = createOuterRepoWithSubdir();
|
||||
try {
|
||||
const result = runGsdTools('init new-project', outer);
|
||||
assert.ok(result.success, `init new-project failed: ${result.error}`);
|
||||
|
||||
const payload = JSON.parse(result.output);
|
||||
assert.strictEqual(payload.has_git, true, 'has_git must be true at the worktree root');
|
||||
assert.strictEqual(normalizePath(payload.git_worktree_root), normalizePath(outer));
|
||||
assert.strictEqual(
|
||||
payload.in_nested_subdir,
|
||||
false,
|
||||
'at the worktree root, in_nested_subdir must be false',
|
||||
);
|
||||
} finally {
|
||||
cleanup(outer);
|
||||
}
|
||||
});
|
||||
|
||||
test('bug-3491: init new-project reports has_git: false outside any git worktree', () => {
|
||||
const tmp = fs.realpathSync.native(fs.mkdtempSync(path.join(os.tmpdir(), 'bug-3491-bare-')));
|
||||
try {
|
||||
const result = runGsdTools('init new-project', tmp);
|
||||
assert.ok(result.success, `init new-project failed: ${result.error}`);
|
||||
const payload = JSON.parse(result.output);
|
||||
assert.strictEqual(payload.has_git, false);
|
||||
assert.strictEqual(payload.in_nested_subdir, false);
|
||||
assert.strictEqual(payload.git_worktree_root, null);
|
||||
} finally {
|
||||
cleanup(tmp);
|
||||
}
|
||||
});
|
||||
|
||||
test('bug-3491: init ingest-docs mirrors the same has_git semantics', () => {
|
||||
// ingest-docs.md has the same shallow check and the same nested-init risk.
|
||||
const { outer, subdir } = createOuterRepoWithSubdir('bug-3491-ingest-');
|
||||
try {
|
||||
const result = runGsdTools('init ingest-docs', subdir);
|
||||
assert.ok(result.success, `init ingest-docs failed: ${result.error}`);
|
||||
const payload = JSON.parse(result.output);
|
||||
assert.strictEqual(
|
||||
payload.has_git,
|
||||
true,
|
||||
'init ingest-docs must also detect parent worktree (#3491 related path)',
|
||||
);
|
||||
assert.strictEqual(normalizePath(payload.git_worktree_root), normalizePath(outer));
|
||||
assert.strictEqual(payload.in_nested_subdir, true);
|
||||
} finally {
|
||||
cleanup(outer);
|
||||
}
|
||||
});
|
||||
|
||||
// ─── Workflow-text test: the deployed `new-project.md` must gate `git init` ─
|
||||
|
||||
test('bug-3491: new-project.md gates `git init` on in_nested_subdir, not just has_git', () => {
|
||||
const content = fs.readFileSync(WORKFLOW_PATH, 'utf-8');
|
||||
|
||||
// The pre-fix workflow had the literal sequence:
|
||||
//
|
||||
// **If `has_git` is false:** Initialize git:
|
||||
// ```bash
|
||||
// git init
|
||||
// ```
|
||||
//
|
||||
// …which fires for any subdirectory of an existing repo. The fix must
|
||||
// either gate the init on `in_nested_subdir`/worktree-root semantics or
|
||||
// drop the unconditional `git init` block entirely.
|
||||
const unconditionalInitPattern =
|
||||
/\*\*If `has_git` is false:\*\* Initialize git:\s*\r?\n+```bash\s*\r?\ngit init\s*\r?\n```/;
|
||||
assert.ok(
|
||||
!unconditionalInitPattern.test(content),
|
||||
'new-project.md must not run `git init` unconditionally on has_git=false (#3491). ' +
|
||||
'Gate it on `in_nested_subdir === false` so the workflow refuses to create ' +
|
||||
'a nested .git inside an existing worktree.',
|
||||
);
|
||||
|
||||
// The fixed workflow MUST mention the new field so reviewers can see the
|
||||
// gating exists. (Workflow markdown IS the deployed product — testing it
|
||||
// as text is the only end-to-end signal we have.)
|
||||
assert.ok(
|
||||
/in_nested_subdir/.test(content),
|
||||
'new-project.md must reference `in_nested_subdir` after the #3491 fix',
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user