Merge pull request #263 from open-gsd/fix/187-delete-parity-tests

chore(#187): delete all *-parity.test.cjs files
This commit is contained in:
Tom Boucher
2026-05-25 09:25:48 -04:00
committed by GitHub
8 changed files with 0 additions and 648 deletions

View File

@@ -1,42 +0,0 @@
'use strict';
/**
* For every `agents/gsd-*.md`, assert its agent name appears as a row
* in docs/INVENTORY.md's Agents table. AGENTS.md card presence is NOT
* enforced — that file is allowed to be a curated subset (primary
* cards + advanced stubs).
*
* Related: docs readiness refresh, lane-12 recommendation.
*/
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const ROOT = path.resolve(__dirname, '..');
const AGENTS_DIR = path.join(ROOT, 'agents');
const INVENTORY_MD = fs.readFileSync(path.join(ROOT, 'docs', 'INVENTORY.md'), 'utf8');
const agentFiles = fs
.readdirSync(AGENTS_DIR)
.filter((f) => /^gsd-.*\.md$/.test(f));
function mentionedInInventoryAgents(name) {
// Row form in the Agents table: `| agent-name | role | ... |`
// The Agents table uses the raw name (no code fence) in column 1.
const rowRe = new RegExp(`^\\|\\s*${name.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\$&')}\\s*\\|`, 'm');
return rowRe.test(INVENTORY_MD);
}
describe('every shipped agent has a row in INVENTORY.md', () => {
for (const file of agentFiles) {
const name = file.replace(/\.md$/, '');
test(name, () => {
assert.ok(
mentionedInInventoryAgents(name),
`agents/${file} has no row in docs/INVENTORY.md Agents table — add one`,
);
});
}
});

View File

@@ -1,126 +0,0 @@
'use strict';
/**
* Regression test for #2687 — loadConfig must not emit "unknown config key"
* warnings for keys that are registered in DYNAMIC_KEY_PATTERNS (e.g. review,
* model_profile_overrides, claude_md_assembly). These keys were absent from
* the hand-maintained KNOWN_TOP_LEVEL set in core.cjs, causing false-positive
* warnings on every read.
*
* We trigger loadConfig via `resolve-model` (which calls loadConfig internally)
* and assert that stderr is EMPTY on success — a typed-IR equivalent of
* "no warning was emitted" without grepping for specific warning text. The
* absence of any stderr output IS the contract: loadConfig prints nothing
* to stderr when every top-level key in config.json is recognized.
*
* Migrated from substring `.includes('unknown config key')` text matching
* to typed empty-stderr assertions per #2974.
*/
const { describe, test, afterEach } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const { spawnSync } = require('node:child_process');
const { createTempProject, cleanup, TOOLS_PATH } = require('./helpers.cjs');
const TEST_ENV_BASE = {
GSD_SESSION_KEY: '',
CODEX_THREAD_ID: '',
CLAUDE_SESSION_ID: '',
CLAUDE_CODE_SSE_PORT: '',
OPENCODE_SESSION_ID: '',
GEMINI_SESSION_ID: '',
CURSOR_SESSION_ID: '',
WINDSURF_SESSION_ID: '',
TERM_SESSION_ID: '',
WT_SESSION: '',
TMUX_PANE: '',
ZELLIJ_SESSION_NAME: '',
TTY: '',
SSH_TTY: '',
};
/**
* Run gsd-tools and return { stdout, stderr, status }.
* Captures stderr even when the process exits 0 (unlike runGsdTools which only
* surfaces stderr via result.error on non-zero exit).
*/
function runWithStderr(args, cwd) {
const result = spawnSync(process.execPath, [TOOLS_PATH, ...args], {
cwd,
encoding: 'utf-8',
env: { ...process.env, ...TEST_ENV_BASE },
});
return {
stdout: result.stdout || '',
stderr: result.stderr || '',
status: result.status,
};
}
describe('bug-2687 — no warning for dynamic-pattern containers in loadConfig', () => {
let tmpDir;
afterEach(() => {
if (tmpDir) cleanup(tmpDir);
tmpDir = null;
});
test('review — loadConfig emits no warning when config.json contains review key', () => {
tmpDir = createTempProject('gsd-2687-review-');
const configPath = path.join(tmpDir, '.planning', 'config.json');
fs.writeFileSync(
configPath,
JSON.stringify({ review: { models: { 'test-cli': 'test-command' } } }, null, 2),
'utf-8'
);
// resolve-model calls loadConfig internally, triggering the KNOWN_TOP_LEVEL check
const result = runWithStderr(['resolve-model', 'planner'], tmpDir);
assert.equal(
result.stderr.trim(),
'',
`loadConfig must emit no stderr output for valid dynamic-pattern keys (#2687) — got: ${result.stderr}`
);
});
test('model_profile_overrides — loadConfig emits no warning when config.json contains model_profile_overrides key', () => {
tmpDir = createTempProject('gsd-2687-mpo-');
const configPath = path.join(tmpDir, '.planning', 'config.json');
fs.writeFileSync(
configPath,
JSON.stringify({ model_profile_overrides: { codex: { sonnet: 'claude-sonnet-4' } } }, null, 2),
'utf-8'
);
// resolve-model calls loadConfig internally, triggering the KNOWN_TOP_LEVEL check
const result = runWithStderr(['resolve-model', 'planner'], tmpDir);
assert.equal(
result.stderr.trim(),
'',
`loadConfig must emit no stderr output for valid dynamic-pattern keys (#2687) — got: ${result.stderr}`
);
});
test('claude_md_assembly — loadConfig emits no warning when config.json contains claude_md_assembly key', () => {
tmpDir = createTempProject('gsd-2687-cma-');
const configPath = path.join(tmpDir, '.planning', 'config.json');
fs.writeFileSync(
configPath,
JSON.stringify({ claude_md_assembly: { mode: 'custom', blocks: { identity: true } } }, null, 2),
'utf-8'
);
// resolve-model calls loadConfig internally, triggering the KNOWN_TOP_LEVEL check
const result = runWithStderr(['resolve-model', 'planner'], tmpDir);
assert.equal(
result.stderr.trim(),
'',
`loadConfig must emit no stderr output for valid dynamic-pattern keys (#2687) — got: ${result.stderr}`
);
});
});

View File

@@ -1,40 +0,0 @@
'use strict';
/**
* For every `get-shit-done/bin/lib/*.cjs`, assert the module name
* appears as a row in docs/INVENTORY.md's CLI Modules table.
* docs/CLI-TOOLS.md is allowed to describe a subset (narrative doc);
* INVENTORY.md is the authoritative module roster.
*
* Related: docs readiness refresh, lane-12 recommendation.
*/
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const ROOT = path.resolve(__dirname, '..');
const LIB_DIR = path.join(ROOT, 'get-shit-done', 'bin', 'lib');
const INVENTORY_MD = fs.readFileSync(path.join(ROOT, 'docs', 'INVENTORY.md'), 'utf8');
const moduleFiles = fs
.readdirSync(LIB_DIR)
.filter((f) => f.endsWith('.cjs'));
function mentionedInInventoryCliModules(filename) {
// Row form: | `filename.cjs` | responsibility |
const rowRe = new RegExp(`\\|\\s*\\\`${filename.replace(/\./g, '\\.')}\\\`\\s*\\|`, 'm');
return rowRe.test(INVENTORY_MD);
}
describe('every CLI module has a row in INVENTORY.md', () => {
for (const file of moduleFiles) {
test(file, () => {
assert.ok(
mentionedInInventoryCliModules(file),
`get-shit-done/bin/lib/${file} has no row in docs/INVENTORY.md CLI Modules table — add one`,
);
});
}
});

View File

@@ -1,78 +0,0 @@
// allow-test-rule: source-text-is-the-product
'use strict';
/**
* For every `commands/gsd/*.md`, assert its `/gsd-<name>` slash command
* appears either (a) as a `### /gsd-...` heading in docs/COMMANDS.md or
* (b) as a row in docs/INVENTORY.md's Commands table. At least one of
* these must be true so every shipped command is reachable from docs.
*
* The slug is derived from the `name:` frontmatter field (e.g. `gsd-workflow`)
* rather than the filename (e.g. `ns-workflow.md`), so the test stays aligned
* with the actual deployed command token even when the file has a legacy name.
*
* Related: docs readiness refresh, lane-12 recommendation.
*/
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const ROOT = path.resolve(__dirname, '..');
const COMMANDS_DIR = path.join(ROOT, 'commands', 'gsd');
const COMMANDS_MD = fs.readFileSync(path.join(ROOT, 'docs', 'COMMANDS.md'), 'utf8');
const INVENTORY_MD = fs.readFileSync(path.join(ROOT, 'docs', 'INVENTORY.md'), 'utf8');
const commandFiles = fs.readdirSync(COMMANDS_DIR).filter((f) => f.endsWith('.md'));
/**
* Extract the slug from the `name:` frontmatter field.
* Accepts both `gsd:slug` and `gsd-slug` forms.
* Returns the slug portion only (e.g. `workflow`, `plan-phase`).
* Throws if frontmatter is missing or malformed.
*/
function parseSlugFromFrontmatter(content, filePath) {
// allow-test-rule: validating YAML frontmatter delimiter structure, not application source
if (!content.startsWith('---')) {
throw new Error('commands-doc-parity: missing YAML frontmatter in ' + filePath);
}
const closingIdx = content.indexOf('\n---', 3);
if (closingIdx < 0) {
throw new Error('commands-doc-parity: unclosed YAML frontmatter in ' + filePath);
}
const frontmatter = content.slice(0, closingIdx);
const nameMatch = frontmatter.match(/^name:\s*"?(gsd[:-])([a-z0-9][a-z0-9-]*)"?\s*$/m);
if (!nameMatch) {
throw new Error('commands-doc-parity: could not extract slug from name: field in ' + filePath);
}
return nameMatch[2];
}
function mentionedInCommandsDoc(slug) {
// Match a heading like: ### /gsd-<slug> or ## /gsd-<slug>
const headingRe = new RegExp(`^#{2,4}\\s+\\\`?/gsd-${slug}\\\`?(?:[\\s(]|$)`, 'm');
return headingRe.test(COMMANDS_MD);
}
function mentionedInInventory(slug) {
// Match a row like: | `/gsd-<slug>` | ... |
const rowRe = new RegExp(`\\|\\s*\\\`/gsd-${slug}\\\`\\s*\\|`, 'm');
return rowRe.test(INVENTORY_MD);
}
describe('every shipped command is documented somewhere', () => {
for (const file of commandFiles) {
const filePath = path.join(COMMANDS_DIR, file);
const content = fs.readFileSync(filePath, 'utf8');
const slug = parseSlugFromFrontmatter(content, filePath);
test(`/gsd-${slug}`, () => {
const inCommandsDoc = mentionedInCommandsDoc(slug);
const inInventory = mentionedInInventory(slug);
assert.ok(
inCommandsDoc || inInventory,
`commands/gsd/${file} (name: gsd-${slug}) is not mentioned in docs/COMMANDS.md (as a heading) or docs/INVENTORY.md (as a Commands row) — add a one-line entry to at least one`,
);
});
}
});

View File

@@ -1,44 +0,0 @@
'use strict';
// allow-test-rule: source-text-is-the-product
// Reads .md/.json/.yml product files whose deployed text IS what the
// runtime loads — testing text content tests the deployed contract.
/**
* Asserts every exact-match key in config-schema.cjs appears at least once
* in docs/CONFIGURATION.md. A key present in the validator but absent from
* the docs means users can set it but have no guidance. A key in the docs but
* absent from the validator means config-set silently rejects it.
*
* Dynamic patterns (agent_skills.*, review.models.*, features.*) are excluded
* from this check — they are documented by namespace in CONFIGURATION.md.
*/
const { test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const ROOT = path.resolve(__dirname, '..');
const { VALID_CONFIG_KEYS } = require('../get-shit-done/bin/lib/config-schema.cjs');
const CONFIGURATION_MD = fs.readFileSync(path.join(ROOT, 'docs', 'CONFIGURATION.md'), 'utf8');
// Reserved for future internal keys; workflow._auto_chain_active removed from VALID_CONFIG_KEYS (#2530).
const INTERNAL_KEYS = new Set();
test('every key in VALID_CONFIG_KEYS is documented in docs/CONFIGURATION.md', () => {
const undocumented = [];
for (const key of VALID_CONFIG_KEYS) {
if (INTERNAL_KEYS.has(key)) continue;
if (!CONFIGURATION_MD.includes('`' + key + '`')) {
undocumented.push(key);
}
}
assert.deepStrictEqual(
undocumented,
[],
'Keys in VALID_CONFIG_KEYS with no mention in docs/CONFIGURATION.md:\n' +
undocumented.map((k) => ' ' + k).join('\n') +
'\nAdd a row in the appropriate section of docs/CONFIGURATION.md.',
);
});

View File

@@ -1,147 +0,0 @@
'use strict';
// allow-test-rule: structural source assertion — verifies that sdk/src/query/config-schema.ts
// is a re-export shell (no inline literals) rather than a redeclared Set. Runtime/IR comparison
// cannot distinguish a re-export from a redeclared `new Set([...])` with identical contents
// because both would deep-equal the manifest set; only source-shape inspection catches drift
// back to inline literals. See test "SDK config-schema.ts re-exports from configuration module".
/**
* Manifest-as-source-of-truth guard (Phase 2, Cycle 5, #3536).
*
* Prior to Cycle 5, the CJS and SDK schema files each had independent inline
* literals. This test existed to prevent drift between them. After Cycle 5,
* BOTH sides derive their data from sdk/shared/config-schema.manifest.json,
* so there is nothing to drift — but the guard still serves a purpose:
*
* 1. Confirm that VALID_CONFIG_KEYS loaded at runtime from config-schema.cjs
* exactly matches the manifest's validKeys array.
* 2. Confirm that VALID_CONFIG_KEYS exported from sdk/src/query/config-schema.ts
* (via sdk/dist/) equals the same manifest.
* 3. Confirm that DYNAMIC_KEY_PATTERNS from config-schema.cjs have .source
* fields matching the manifest's dynamicKeyPatterns.
* 4. Confirm RUNTIME_STATE_KEYS from config-schema.cjs matches the manifest.
*
* This ensures neither side has accidentally disconnected from the manifest
* (e.g. reverted to inline literals or switched to a different data source).
*/
const { test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const ROOT = path.resolve(__dirname, '..');
const MANIFEST_PATH = path.join(ROOT, 'sdk', 'shared', 'config-schema.manifest.json');
const manifest = JSON.parse(fs.readFileSync(MANIFEST_PATH, 'utf8'));
const manifestValidKeys = new Set(manifest.validKeys);
const manifestRuntimeKeys = new Set(manifest.runtimeStateKeys);
const manifestPatternSources = manifest.dynamicKeyPatterns.map((p) => p.source);
const {
VALID_CONFIG_KEYS: CJS_KEYS,
RUNTIME_STATE_KEYS: CJS_RUNTIME_KEYS,
DYNAMIC_KEY_PATTERNS: CJS_PATTERNS,
} = require('../get-shit-done/bin/lib/config-schema.cjs');
// ─── CJS side: verify manifest-sourced values ─────────────────────────────
test('CJS VALID_CONFIG_KEYS matches manifest validKeys exactly', () => {
const missingInCjs = [...manifestValidKeys].filter((k) => !CJS_KEYS.has(k));
const extraInCjs = [...CJS_KEYS].filter((k) => !manifestValidKeys.has(k));
assert.deepStrictEqual(
missingInCjs,
[],
'Manifest keys missing from CJS VALID_CONFIG_KEYS:\n' +
missingInCjs.map((k) => ' ' + k).join('\n'),
);
assert.deepStrictEqual(
extraInCjs,
[],
'CJS VALID_CONFIG_KEYS has keys not in manifest:\n' +
extraInCjs.map((k) => ' ' + k).join('\n'),
);
});
test('CJS RUNTIME_STATE_KEYS matches manifest runtimeStateKeys exactly', () => {
const missingInCjs = [...manifestRuntimeKeys].filter((k) => !CJS_RUNTIME_KEYS.has(k));
const extraInCjs = [...CJS_RUNTIME_KEYS].filter((k) => !manifestRuntimeKeys.has(k));
assert.deepStrictEqual(missingInCjs, [], 'Manifest runtime keys missing from CJS RUNTIME_STATE_KEYS');
assert.deepStrictEqual(extraInCjs, [], 'CJS RUNTIME_STATE_KEYS has keys not in manifest');
});
test('CJS DYNAMIC_KEY_PATTERNS .source fields match manifest dynamicKeyPatterns', () => {
assert.strictEqual(
CJS_PATTERNS.length,
manifestPatternSources.length,
`CJS has ${CJS_PATTERNS.length} patterns but manifest has ${manifestPatternSources.length}`,
);
for (let i = 0; i < manifestPatternSources.length; i++) {
const expected = manifestPatternSources[i];
const actual = CJS_PATTERNS[i].source;
assert.strictEqual(
actual,
expected,
`CJS pattern[${i}].source mismatch: expected "${expected}", got "${actual}"`,
);
}
});
// ─── SDK side: verify config-schema.ts re-exports from configuration module ─
test('SDK config-schema.ts re-exports from config module (not inline literals)', () => {
const SDK_SCHEMA_PATH = path.join(ROOT, 'sdk', 'src', 'query', 'config-schema.ts');
const src = fs.readFileSync(SDK_SCHEMA_PATH, 'utf8');
// After Cycle 5, the file must NOT contain inline key literals.
// It should import/re-export from '../config/index.js'.
assert.ok(
src.includes("from '../config/index.js'"),
'sdk/src/query/config-schema.ts must re-export from ../config/index.js (not inline literals)',
);
// Must NOT contain a standalone new Set([...]) block with key literals.
// A minimal check: the file should not define VALID_CONFIG_KEYS as a Set literal.
assert.ok(
!src.includes("new Set([\n 'mode'") && !src.includes("new Set(['mode'"),
'sdk/src/query/config-schema.ts must not contain an inline VALID_CONFIG_KEYS Set literal',
);
});
// ─── Cross-check: CJS equals SDK via manifest ─────────────────────────────
test('#2653 — CJS and SDK both source from the same manifest (set equality via manifest)', () => {
// Since both sides derive from sdk/shared/config-schema.manifest.json,
// the CJS runtime set must equal the manifest set (verified above).
// This test is the explicit statement of the invariant for audit purposes.
const cjsKeysSorted = [...CJS_KEYS].sort();
const manifestKeysSorted = [...manifestValidKeys].sort();
assert.deepStrictEqual(
cjsKeysSorted,
manifestKeysSorted,
'CJS VALID_CONFIG_KEYS must equal manifest validKeys — both sides source from the manifest',
);
});
test('#2653 — CJS DYNAMIC_KEY_PATTERNS test functions work correctly', () => {
// Verify that each pattern's test() function (reconstructed from manifest source)
// correctly accepts sample keys and rejects non-matching ones.
const samples = [
['agent_skills.gsd-planner', 0],
['review.models.claude', 1],
['features.some_feature', 2],
['claude_md_assembly.blocks.intro', 3],
['model_profile_overrides.codex.opus', 4],
['models.planning', 5],
['dynamic_routing.enabled', 6],
['model_overrides.my-agent', 7],
['review.max_prompt_tokens_per_reviewer.ollama', 8],
];
for (const [key, idx] of samples) {
assert.ok(
CJS_PATTERNS[idx].test(key),
`CJS pattern[${idx}] must accept "${key}"`,
);
}
});

View File

@@ -1,39 +0,0 @@
'use strict';
/**
* For every `hooks/*.(js|sh)`, assert the hook filename appears as a
* row in docs/INVENTORY.md's Hooks table. docs/ARCHITECTURE.md's hook
* table is allowed to lag — INVENTORY.md is authoritative.
*
* Related: docs readiness refresh, lane-12 recommendation.
*/
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const ROOT = path.resolve(__dirname, '..');
const HOOKS_DIR = path.join(ROOT, 'hooks');
const INVENTORY_MD = fs.readFileSync(path.join(ROOT, 'docs', 'INVENTORY.md'), 'utf8');
const hookFiles = fs
.readdirSync(HOOKS_DIR)
.filter((f) => /\.(js|sh)$/.test(f));
function mentionedInInventoryHooks(filename) {
// Row form: | `filename.js` | event | purpose |
const rowRe = new RegExp(`\\|\\s*\\\`${filename.replace(/\./g, '\\.')}\\\`\\s*\\|`, 'm');
return rowRe.test(INVENTORY_MD);
}
describe('every shipped hook has a row in INVENTORY.md', () => {
for (const file of hookFiles) {
test(file, () => {
assert.ok(
mentionedInInventoryHooks(file),
`hooks/${file} has no row in docs/INVENTORY.md Hooks table — add one`,
);
});
}
});

View File

@@ -1,132 +0,0 @@
'use strict';
/**
* Reverse-direction parity: every row declared in docs/INVENTORY.md must
* resolve to a real file on the filesystem. Complements the forward tests
* (actual ⊆ INVENTORY) with the reverse direction (INVENTORY ⊆ actual),
* catching ghost entries left behind when artifacts are deleted or renamed.
*/
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const ROOT = path.resolve(__dirname, '..');
const INVENTORY = fs.readFileSync(path.join(ROOT, 'docs', 'INVENTORY.md'), 'utf8');
/** Extract the text of a named top-level section (## Header ... next ##). */
function section(header) {
const start = INVENTORY.indexOf('## ' + header);
if (start === -1) return '';
const next = INVENTORY.indexOf('\n## ', start + 1);
return next === -1 ? INVENTORY.slice(start) : INVENTORY.slice(start, next);
}
/** Extract backtick-quoted filenames from column-1 table cells. */
function backtickNames(text, ext) {
const re = new RegExp('\\|\\s*`([^`]+\\.' + ext + ')`\\s*\\|', 'gm');
const names = [];
let m;
while ((m = re.exec(text)) !== null) names.push(m[1]);
return names;
}
/** Extract agent names from `| gsd-xxx | ...` rows (no backticks). */
function agentNames(text) {
const re = /^\|\s*(gsd-[a-z0-9-]+)\s*\|/gm;
const names = [];
let m;
while ((m = re.exec(text)) !== null) names.push(m[1]);
return names;
}
/** Extract relative source paths from markdown links in Commands section. */
function commandSourcePaths(text) {
const re = /\[commands\/gsd\/[^\]]+\]\(\.\.\/(commands\/gsd\/[^)]+)\)/g;
const paths = [];
let m;
while ((m = re.exec(text)) !== null) paths.push(m[1]);
return paths;
}
describe('INVENTORY.md declared artifacts exist on the filesystem (ghost-entry guard)', () => {
describe('Agents', () => {
const names = agentNames(section('Agents'));
for (const name of names) {
test(name, () => {
const p = path.join(ROOT, 'agents', name + '.md');
assert.ok(
fs.existsSync(p),
'INVENTORY.md declares agent "' + name + '" but agents/' + name + '.md does not exist — remove the ghost row or restore the file',
);
});
}
});
describe('Commands', () => {
const paths = commandSourcePaths(section('Commands'));
for (const rel of paths) {
test(rel, () => {
const p = path.join(ROOT, rel);
assert.ok(
fs.existsSync(p),
'INVENTORY.md declares source "' + rel + '" but the file does not exist — remove the ghost row or restore the file',
);
});
}
});
describe('Workflows', () => {
const names = backtickNames(section('Workflows'), 'md');
for (const name of names) {
test(name, () => {
const p = path.join(ROOT, 'get-shit-done', 'workflows', name);
assert.ok(
fs.existsSync(p),
'INVENTORY.md declares workflow "' + name + '" but get-shit-done/workflows/' + name + ' does not exist — remove the ghost row or restore the file',
);
});
}
});
describe('References', () => {
const names = backtickNames(section('References'), 'md');
for (const name of names) {
test(name, () => {
const p = path.join(ROOT, 'get-shit-done', 'references', name);
assert.ok(
fs.existsSync(p),
'INVENTORY.md declares reference "' + name + '" but get-shit-done/references/' + name + ' does not exist — remove the ghost row or restore the file',
);
});
}
});
describe('CLI Modules', () => {
const names = backtickNames(section('CLI Modules'), 'cjs');
for (const name of names) {
test(name, () => {
const p = path.join(ROOT, 'get-shit-done', 'bin', 'lib', name);
assert.ok(
fs.existsSync(p),
'INVENTORY.md declares CLI module "' + name + '" but get-shit-done/bin/lib/' + name + ' does not exist — remove the ghost row or restore the file',
);
});
}
});
describe('Hooks', () => {
const jsNames = backtickNames(section('Hooks'), 'js');
const shNames = backtickNames(section('Hooks'), 'sh');
for (const name of [...jsNames, ...shNames]) {
test(name, () => {
const p = path.join(ROOT, 'hooks', name);
assert.ok(
fs.existsSync(p),
'INVENTORY.md declares hook "' + name + '" but hooks/' + name + ' does not exist — remove the ghost row or restore the file',
);
});
}
});
});