From 7c6f8005f363810002451142863b97c1536fa8b8 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Sat, 25 Apr 2026 10:50:54 -0400 Subject: [PATCH] test: destroy 9 config-schema.cjs/core.cjs source-grep tests, replace with behavioral config-set (#2696) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 * 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 * 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 --------- Co-authored-by: Claude Sonnet 4.6 --- tests/agent-frontmatter.test.cjs | 4 ++ tests/agent-skills-awareness.test.cjs | 3 ++ tests/autonomous-allowed-tools.test.cjs | 3 ++ .../bug-2334-quick-gsd-sdk-preflight.test.cjs | 3 ++ .../bug-2346-agent-read-loop-guards.test.cjs | 3 ++ tests/code-review.test.cjs | 27 ++++++----- tests/execute-phase-wave.test.cjs | 21 ++++++--- tests/inline-plan-threshold.test.cjs | 10 ---- tests/plan-bounce.test.cjs | 47 +++++++++++-------- tests/thinking-partner.test.cjs | 40 ++++++++-------- 10 files changed, 93 insertions(+), 68 deletions(-) diff --git a/tests/agent-frontmatter.test.cjs b/tests/agent-frontmatter.test.cjs index c8f4ec25f..139225179 100644 --- a/tests/agent-frontmatter.test.cjs +++ b/tests/agent-frontmatter.test.cjs @@ -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 * diff --git a/tests/agent-skills-awareness.test.cjs b/tests/agent-skills-awareness.test.cjs index ceef38ede..8046adef9 100644 --- a/tests/agent-skills-awareness.test.cjs +++ b/tests/agent-skills-awareness.test.cjs @@ -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'); diff --git a/tests/autonomous-allowed-tools.test.cjs b/tests/autonomous-allowed-tools.test.cjs index 9539d9693..2116d7a9e 100644 --- a/tests/autonomous-allowed-tools.test.cjs +++ b/tests/autonomous-allowed-tools.test.cjs @@ -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'); diff --git a/tests/bug-2334-quick-gsd-sdk-preflight.test.cjs b/tests/bug-2334-quick-gsd-sdk-preflight.test.cjs index 0a103038e..401603cd7 100644 --- a/tests/bug-2334-quick-gsd-sdk-preflight.test.cjs +++ b/tests/bug-2334-quick-gsd-sdk-preflight.test.cjs @@ -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; diff --git a/tests/bug-2346-agent-read-loop-guards.test.cjs b/tests/bug-2346-agent-read-loop-guards.test.cjs index dbdecbd57..6916baff8 100644 --- a/tests/bug-2346-agent-read-loop-guards.test.cjs +++ b/tests/bug-2346-agent-read-loop-guards.test.cjs @@ -21,6 +21,9 @@ const path = require('path'); const AGENTS_DIR = path.join(__dirname, '..', 'agents'); +// allow-test-rule: source-text-is-the-product +// The 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', () => { diff --git a/tests/code-review.test.cjs b/tests/code-review.test.cjs index e33858a8b..7755fec6f 100644 --- a/tests/code-review.test.cjs +++ b/tests/code-review.test.cjs @@ -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', () => { diff --git a/tests/execute-phase-wave.test.cjs b/tests/execute-phase-wave.test.cjs index 65cb62429..7b7440a45 100644 --- a/tests/execute-phase-wave.test.cjs +++ b/tests/execute-phase-wave.test.cjs @@ -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); + } }); }); diff --git a/tests/inline-plan-threshold.test.cjs b/tests/inline-plan-threshold.test.cjs index a7306707d..e8e4265ce 100644 --- a/tests/inline-plan-threshold.test.cjs +++ b/tests/inline-plan-threshold.test.cjs @@ -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( diff --git a/tests/plan-bounce.test.cjs b/tests/plan-bounce.test.cjs index d78a43fec..b87664519 100644 --- a/tests/plan-bounce.test.cjs +++ b/tests/plan-bounce.test.cjs @@ -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; diff --git a/tests/thinking-partner.test.cjs b/tests/thinking-partner.test.cjs index 81758c328..1a12903e9 100644 --- a/tests/thinking-partner.test.cjs +++ b/tests/thinking-partner.test.cjs @@ -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); + } }); });