test: destroy 9 config-schema.cjs/core.cjs source-grep tests, replace with behavioral config-set (#2696)

* test: destroy 9 config-schema.cjs/core.cjs source-grep tests, add behavioral config-set tests (#2691, #2693)

Replace source-grep theater with config-set behavioral tests:
- execute-phase-wave: config-set workflow.use_worktrees replaces VALID_CONFIG_KEYS grep
- inline-plan-threshold: delete redundant source-grep (behavioral test at L36 already covered it)
- plan-bounce: config-set for plan_bounce / plan_bounce_script / plan_bounce_passes replaces 3 key-presence greps
- code-review: config-set for code_review / code_review_depth replaces 2 greps; removes CONFIG_PATH constant
- thinking-partner: config-set features.thinking_partner replaces two greps (config-schema.cjs AND core.cjs)

Behavioral tests survive refactors (no path constants, no file reads). The config-schema.cjs →
core.cjs migration commit 990c3e64 happened because these tests groped source paths.

Add allow-test-rule: source-text-is-the-product annotations to legitimate product-content tests:
autonomous-allowed-tools, agent-frontmatter, agent-skills-awareness, bug-2334, bug-2346,
execute-phase-wave (MD reads), plan-bounce (workflow reads). Annotations explain WHY text
inspection is the right level of testing for AI instruction files.

Closes #2691
Closes #2693

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: address CodeRabbit findings on #2696

- agent-frontmatter.test.cjs: move allow-test-rule annotation from block comment
  to standalone // line comment so rule scanners can detect it
- thinking-partner.test.cjs: strengthen config-set test with config-get read-back
  assertion to verify the value was persisted, not just accepted (exit 0)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: tighten thinking_partner config assertion per CodeRabbit (#2696)

Replace config-get output substring check (includes('true') false-positive
risk) with a direct JSON read of .planning/config.json, asserting the
exact persisted value via strictEqual. This also validates the config file
was created, catching silent key-acceptance without persistence.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-04-25 10:50:54 -04:00
committed by GitHub
parent cd05725576
commit 7c6f8005f3
10 changed files with 93 additions and 68 deletions

View File

@@ -1,3 +1,7 @@
// allow-test-rule: source-text-is-the-product
// Agent .md files are the installed AI agents — their frontmatter and body text IS what
// Claude Code loads at runtime. Checking text content IS checking the deployed contract.
/**
* GSD Agent Frontmatter Tests
*

View File

@@ -1,3 +1,6 @@
// allow-test-rule: source-text-is-the-product
// Agent .md files are the installed AI agents — the "Project skills" block IS the deployed
// instruction. Checking text content IS checking what runs in production.
'use strict';
const { describe, test } = require('node:test');

View File

@@ -12,6 +12,9 @@ const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
// allow-test-rule: source-text-is-the-product
// commands/gsd/autonomous.md is the installed command — its frontmatter is what Claude Code
// reads at runtime to enforce allowed-tools. Checking text content IS checking the contract.
describe('commands/gsd/autonomous.md allowed-tools', () => {
test('includes Agent in allowed-tools list', () => {
const filePath = path.join(__dirname, '..', 'commands', 'gsd', 'autonomous.md');

View File

@@ -19,6 +19,9 @@ const path = require('path');
const WORKFLOW_PATH = path.join(__dirname, '..', 'get-shit-done', 'workflows', 'quick.md');
// allow-test-rule: source-text-is-the-product
// quick.md is the AI instruction workflow — the `command -v gsd-sdk` guard IS the fix.
// There is no behavioral equivalent: the check runs inside the AI agent, not in gsd-tools.
describe('bug #2334: quick workflow gsd-sdk pre-flight check', () => {
let content;

View File

@@ -21,6 +21,9 @@ const path = require('path');
const AGENTS_DIR = path.join(__dirname, '..', 'agents');
// allow-test-rule: source-text-is-the-product
// The <critical_rules> block in agent .md files IS the fix — it is the AI instruction that
// prevents unbounded Read loops. There is no behavioral equivalent without a live LLM run.
describe('bug #2346: agent read loop guards', () => {
describe('gsd-ui-checker', () => {

View File

@@ -28,7 +28,6 @@ const { runGsdTools, createTempProject, cleanup } = require('./helpers.cjs');
const AGENTS_DIR = path.join(__dirname, '..', 'agents');
const COMMANDS_DIR = path.join(__dirname, '..', 'commands', 'gsd');
const WORKFLOWS_DIR = path.join(__dirname, '..', 'get-shit-done', 'workflows');
const CONFIG_PATH = path.join(__dirname, '..', 'get-shit-done', 'bin', 'lib', 'config-schema.cjs');
// Plugin directory resolution (cross-platform safe)
const PLUGIN_WORKFLOWS_DIR = process.env.GSD_PLUGIN_ROOT || path.join(os.homedir(), '.claude', 'get-shit-done', 'workflows');
@@ -271,18 +270,24 @@ describe('CR-WORKFLOW: code review workflow structure', () => {
// --- CR-CONFIG: config key registration ---
describe('CR-CONFIG: config key registration', () => {
test('config.cjs contains workflow.code_review key', () => {
const content = fs.readFileSync(CONFIG_PATH, 'utf-8');
assert.ok(content.includes('workflow.code_review'),
'config.cjs missing workflow.code_review key registration');
test('config-set accepts workflow.code_review', () => {
const tmpDir = createTempProject();
try {
const result = runGsdTools('config-set workflow.code_review true', tmpDir);
assert.ok(result.success, `config-set should accept workflow.code_review: ${result.error}`);
} finally {
cleanup(tmpDir);
}
});
test('config.cjs contains workflow.code_review_depth key', () => {
const content = fs.readFileSync(CONFIG_PATH, 'utf-8');
assert.ok(content.includes('workflow.code_review_depth'),
'config.cjs missing workflow.code_review_depth key registration');
test('config-set accepts workflow.code_review_depth', () => {
const tmpDir = createTempProject();
try {
const result = runGsdTools('config-set workflow.code_review_depth standard', tmpDir);
assert.ok(result.success, `config-set should accept workflow.code_review_depth: ${result.error}`);
} finally {
cleanup(tmpDir);
}
});
test('gsd-tools config-get workflow.code_review succeeds', () => {

View File

@@ -12,12 +12,17 @@ const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const { runGsdTools, createTempProject, cleanup } = require('./helpers.cjs');
const COMMAND_PATH = path.join(__dirname, '..', 'commands', 'gsd', 'execute-phase.md');
const WORKFLOW_PATH = path.join(__dirname, '..', 'get-shit-done', 'workflows', 'execute-phase.md');
const COMMANDS_DOC_PATH = path.join(__dirname, '..', 'docs', 'COMMANDS.md');
const HELP_PATH = path.join(__dirname, '..', 'get-shit-done', 'workflows', 'help.md');
// allow-test-rule: source-text-is-the-product
// The workflow and command .md files are the installed AI instructions — their text content
// IS what executes. String presence tests guard against accidental deletion of critical clauses.
// See #2692 for the missing behavioral test for --wave N argument parsing.
describe('execute-phase command: --wave flag', () => {
test('command file exists', () => {
assert.ok(fs.existsSync(COMMAND_PATH), 'commands/gsd/execute-phase.md should exist');
@@ -128,7 +133,6 @@ describe('use_worktrees config: cross-workflow structural coverage', () => {
const DIAGNOSE_PATH = path.join(__dirname, '..', 'get-shit-done', 'workflows', 'diagnose-issues.md');
const EXECUTE_PLAN_PATH = path.join(__dirname, '..', 'get-shit-done', 'workflows', 'execute-plan.md');
const PLANNING_CONFIG_PATH = path.join(__dirname, '..', 'get-shit-done', 'references', 'planning-config.md');
const CONFIG_CJS_PATH = path.join(__dirname, '..', 'get-shit-done', 'bin', 'lib', 'config-schema.cjs');
test('quick workflow reads USE_WORKTREES from config', () => {
const content = fs.readFileSync(QUICK_PATH, 'utf-8');
@@ -174,11 +178,14 @@ describe('use_worktrees config: cross-workflow structural coverage', () => {
);
});
test('config.cjs includes workflow.use_worktrees in VALID_CONFIG_KEYS', () => {
const content = fs.readFileSync(CONFIG_CJS_PATH, 'utf-8');
assert.ok(
content.includes("'workflow.use_worktrees'"),
'config.cjs VALID_CONFIG_KEYS should include workflow.use_worktrees'
);
test('config-set accepts workflow.use_worktrees', () => {
// allow-test-rule: behavioral — exercises config-set validation, not source text
const tmpDir = createTempProject();
try {
const result = runGsdTools('config-set workflow.use_worktrees true', tmpDir);
assert.ok(result.success, `config-set should accept workflow.use_worktrees: ${result.error}`);
} finally {
cleanup(tmpDir);
}
});
});

View File

@@ -20,7 +20,6 @@ const { runGsdTools, createTempProject, cleanup } = require('./helpers.cjs');
const repoRoot = path.resolve(__dirname, '..');
const executePlanPath = path.join(repoRoot, 'get-shit-done', 'workflows', 'execute-plan.md');
const planningConfigPath = path.join(repoRoot, 'get-shit-done', 'references', 'planning-config.md');
const configCjsPath = path.join(repoRoot, 'get-shit-done', 'bin', 'lib', 'config-schema.cjs');
describe('inline_plan_threshold config key (#1979)', () => {
let tmpDir;
@@ -43,15 +42,6 @@ describe('inline_plan_threshold config key (#1979)', () => {
assert.ok(result.success, `config-set should accept 0: ${result.error}`);
});
test('VALID_CONFIG_KEYS in config.cjs contains workflow.inline_plan_threshold', () => {
const content = fs.readFileSync(configCjsPath, 'utf-8');
assert.match(
content,
/['"]workflow\.inline_plan_threshold['"]/,
'workflow.inline_plan_threshold must be in VALID_CONFIG_KEYS'
);
});
test('planning-config.md documents workflow.inline_plan_threshold', () => {
const content = fs.readFileSync(planningConfigPath, 'utf-8');
assert.match(

View File

@@ -15,35 +15,41 @@ const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const { runGsdTools, createTempProject, cleanup } = require('./helpers.cjs');
const GSD_ROOT = path.join(__dirname, '..', 'get-shit-done');
const CONFIG_CJS_PATH = path.join(GSD_ROOT, 'bin', 'lib', 'config-schema.cjs');
const CONFIG_TEMPLATE_PATH = path.join(GSD_ROOT, 'templates', 'config.json');
const PLAN_PHASE_PATH = path.join(GSD_ROOT, 'workflows', 'plan-phase.md');
describe('Plan Bounce: config keys', () => {
test('workflow.plan_bounce is in VALID_CONFIG_KEYS', () => {
const content = fs.readFileSync(CONFIG_CJS_PATH, 'utf-8');
assert.ok(
content.includes("'workflow.plan_bounce'"),
'VALID_CONFIG_KEYS should contain workflow.plan_bounce'
);
test('config-set accepts workflow.plan_bounce', () => {
const tmpDir = createTempProject();
try {
const result = runGsdTools('config-set workflow.plan_bounce true', tmpDir);
assert.ok(result.success, `config-set should accept workflow.plan_bounce: ${result.error}`);
} finally {
cleanup(tmpDir);
}
});
test('workflow.plan_bounce_script is in VALID_CONFIG_KEYS', () => {
const content = fs.readFileSync(CONFIG_CJS_PATH, 'utf-8');
assert.ok(
content.includes("'workflow.plan_bounce_script'"),
'VALID_CONFIG_KEYS should contain workflow.plan_bounce_script'
);
test('config-set accepts workflow.plan_bounce_script', () => {
const tmpDir = createTempProject();
try {
const result = runGsdTools('config-set workflow.plan_bounce_script ./bounce.sh', tmpDir);
assert.ok(result.success, `config-set should accept workflow.plan_bounce_script: ${result.error}`);
} finally {
cleanup(tmpDir);
}
});
test('workflow.plan_bounce_passes is in VALID_CONFIG_KEYS', () => {
const content = fs.readFileSync(CONFIG_CJS_PATH, 'utf-8');
assert.ok(
content.includes("'workflow.plan_bounce_passes'"),
'VALID_CONFIG_KEYS should contain workflow.plan_bounce_passes'
);
test('config-set accepts workflow.plan_bounce_passes', () => {
const tmpDir = createTempProject();
try {
const result = runGsdTools('config-set workflow.plan_bounce_passes 2', tmpDir);
assert.ok(result.success, `config-set should accept workflow.plan_bounce_passes: ${result.error}`);
} finally {
cleanup(tmpDir);
}
});
});
@@ -76,6 +82,9 @@ describe('Plan Bounce: config template defaults', () => {
});
});
// allow-test-rule: source-text-is-the-product
// plan-phase.md is the installed AI workflow instruction — its text content IS what executes.
// String presence tests guard against accidental deletion of bounce step clauses.
describe('Plan Bounce: plan-phase.md step 12.5', () => {
let content;

View File

@@ -2,6 +2,7 @@ const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const { runGsdTools, createTempProject, cleanup } = require('./helpers.cjs');
const GSD_ROOT = path.join(__dirname, '..', 'get-shit-done');
@@ -66,27 +67,24 @@ describe('Thinking Partner Integration (#1726)', () => {
// Config tests
describe('Config integration', () => {
test('features.thinking_partner is in VALID_CONFIG_KEYS', () => {
const configSrc = fs.readFileSync(
path.join(GSD_ROOT, 'bin', 'lib', 'config-schema.cjs'),
'utf-8'
);
assert.ok(
configSrc.includes("'features.thinking_partner'"),
'VALID_CONFIG_KEYS should contain features.thinking_partner'
);
});
test('features is in KNOWN_TOP_LEVEL section containers', () => {
const coreSrc = fs.readFileSync(
path.join(GSD_ROOT, 'bin', 'lib', 'core.cjs'),
'utf-8'
);
// The KNOWN_TOP_LEVEL set should include 'features' in section containers
assert.ok(
coreSrc.includes("'features'"),
'KNOWN_TOP_LEVEL should contain features as a section container'
);
test('config-set accepts features.thinking_partner', () => {
// Exercises VALID_CONFIG_KEYS membership and KNOWN_TOP_LEVEL acceptance in one call.
// Replaces two source-grep tests that read config-schema.cjs and core.cjs (see #2691).
const tmpDir = createTempProject();
try {
const setResult = runGsdTools('config-set features.thinking_partner true', tmpDir);
assert.ok(setResult.success, `config-set should accept features.thinking_partner: ${setResult.error}`);
const configPath = path.join(tmpDir, '.planning', 'config.json');
assert.ok(fs.existsSync(configPath), 'config-set should create .planning/config.json');
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
assert.strictEqual(
config.features?.thinking_partner,
true,
'config-set should persist features.thinking_partner=true'
);
} finally {
cleanup(tmpDir);
}
});
});