Files
get-shit-done/tests/bug-2418-antigravity-bare-path.test.cjs
Tom Boucher dfa1ecce99 fix(#2418,#2399,#2419,#2421): four workflow and installer bug fixes (#2462)
- #2418: convertClaudeToAntigravityContent now replaces bare ~/.claude and
  $HOME/.claude (no trailing slash) for both global and local installs,
  eliminating the "unreplaced .claude path reference" warnings in
  gsd-debugger.md and update.md during Antigravity installs.

- #2399: plan-phase workflow gains step 13c that commits PLAN.md files
  and STATE.md via gsd-sdk query commit when commit_docs is true.
  Previously commit_docs:true was read but never acted on in plan-phase.

- #2419: new-project.md and new-milestone.md now parse agents_installed
  and missing_agents from the init JSON and warn users clearly when GSD
  agents are not installed, rather than silently failing with "agent type
  not found" when trying to spawn gsd-project-researcher subagents.

- #2421: gsd-planner.md gains a "Grep gate hygiene" rule immediately after
  the Nyquist Rule explaining the self-invalidating grep gate anti-pattern
  and providing comment-stripping alternatives (grep -v, ast-grep).

Tests: 4 new test files (30 tests) all passing.

Closes #2418
Closes #2399
Closes #2419
Closes #2421

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 10:09:33 -04:00

147 lines
5.9 KiB
JavaScript

/**
* Bug #2418: Found unreplaced .claude path reference(s) in Antigravity install
*
* The Antigravity path converter handles ~/.claude/ (with trailing slash) but
* misses bare ~/.claude (without trailing slash), leaving unreplaced references
* that cause the installer to warn about leaked paths.
*
* Files affected: agents/gsd-debugger.md (configDir = ~/.claude) and
* get-shit-done/workflows/update.md (comment with e.g. ~/.claude).
*/
process.env.GSD_TEST_MODE = '1';
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const { convertClaudeToAntigravityContent } = require('../bin/install.js');
describe('convertClaudeToAntigravityContent bare path replacement (#2418)', () => {
describe('global install', () => {
test('replaces ~/.claude (bare, no trailing slash) with ~/.gemini/antigravity', () => {
const input = 'configDir = ~/.claude';
const result = convertClaudeToAntigravityContent(input, true);
assert.ok(
result.includes('~/.gemini/antigravity'),
`Expected ~/.gemini/antigravity in output, got: ${result}`
);
assert.ok(
!result.includes('~/.claude'),
`Expected ~/ .claude to be replaced, got: ${result}`
);
});
test('replaces $HOME/.claude (bare, no trailing slash) with $HOME/.gemini/antigravity', () => {
const input = 'export DIR=$HOME/.claude';
const result = convertClaudeToAntigravityContent(input, true);
assert.ok(
result.includes('$HOME/.gemini/antigravity'),
`Expected $HOME/.gemini/antigravity in output, got: ${result}`
);
assert.ok(
!result.includes('$HOME/.claude'),
`Expected $HOME/.claude to be replaced, got: ${result}`
);
});
test('handles bare ~/.claude followed by comma (comment context)', () => {
const input = '# e.g. ~/.claude, ~/.config/opencode';
const result = convertClaudeToAntigravityContent(input, true);
assert.ok(
!result.includes('~/.claude'),
`Expected ~/ .claude to be replaced in comment context, got: ${result}`
);
});
test('still replaces ~/.claude/ (with trailing slash) correctly', () => {
const input = 'See ~/.claude/get-shit-done/workflows/';
const result = convertClaudeToAntigravityContent(input, true);
assert.ok(
result.includes('~/.gemini/antigravity/get-shit-done/workflows/'),
`Expected path with trailing slash to be replaced, got: ${result}`
);
assert.ok(!result.includes('~/.claude/'), `Expected ~/ .claude/ to be fully replaced, got: ${result}`);
});
test('does not double-replace ~/.claude/ paths', () => {
const input = 'See ~/.claude/get-shit-done/';
const result = convertClaudeToAntigravityContent(input, true);
// Result should contain exactly one occurrence of the replacement path
const count = (result.match(/~\/.gemini\/antigravity\//g) || []).length;
assert.strictEqual(count, 1, `Expected exactly 1 replacement, got ${count} in: ${result}`);
});
});
describe('local install', () => {
test('replaces ~/.claude (bare, no trailing slash) with .agent', () => {
const input = 'configDir = ~/.claude';
const result = convertClaudeToAntigravityContent(input, false);
assert.ok(
result.includes('.agent'),
`Expected .agent in output, got: ${result}`
);
assert.ok(
!result.includes('~/.claude'),
`Expected ~/ .claude to be replaced, got: ${result}`
);
});
test('replaces $HOME/.claude (bare, no trailing slash) with .agent', () => {
const input = 'export DIR=$HOME/.claude';
const result = convertClaudeToAntigravityContent(input, false);
assert.ok(
result.includes('.agent'),
`Expected .agent in output, got: ${result}`
);
assert.ok(
!result.includes('$HOME/.claude'),
`Expected $HOME/.claude to be replaced, got: ${result}`
);
});
test('does not double-replace ~/.claude/ paths', () => {
const input = 'See ~/.claude/get-shit-done/';
const result = convertClaudeToAntigravityContent(input, false);
// .agent/ should appear exactly once
const count = (result.match(/\.agent\//g) || []).length;
assert.strictEqual(count, 1, `Expected exactly 1 replacement, got ${count} in: ${result}`);
});
});
describe('installed files contain no bare ~/.claude references after conversion', () => {
const fs = require('fs');
const path = require('path');
const repoRoot = path.join(__dirname, '..');
// The scanner regex used by the installer to detect leaked paths
const leakedPathRegex = /(?:~|\$HOME)\/\.claude\b/g;
function convertFile(filePath, isGlobal) {
const content = fs.readFileSync(filePath, 'utf8');
return convertClaudeToAntigravityContent(content, isGlobal);
}
test('gsd-debugger.md has no leaked ~/.claude after global Antigravity conversion', () => {
const debuggerPath = path.join(repoRoot, 'agents', 'gsd-debugger.md');
if (!fs.existsSync(debuggerPath)) return; // skip if file doesn't exist
const converted = convertFile(debuggerPath, true);
const matches = converted.match(leakedPathRegex);
assert.strictEqual(
matches, null,
`gsd-debugger.md still contains leaked .claude paths after Antigravity conversion: ${matches}`
);
});
test('update.md has no leaked ~/.claude after global Antigravity conversion', () => {
const updatePath = path.join(repoRoot, 'get-shit-done', 'workflows', 'update.md');
if (!fs.existsSync(updatePath)) return; // skip if file doesn't exist
const converted = convertFile(updatePath, true);
const matches = converted.match(leakedPathRegex);
assert.strictEqual(
matches, null,
`update.md still contains leaked .claude paths after Antigravity conversion: ${matches}`
);
});
});
});