* test(#3338): fold the verify/validate & workflow-text issue-* cluster — Wave 6 Folds 10 legacy issue-*.test.cjs regression files (75 test() blocks) into their module's main suite, per H3 (#3315) of the test-hygiene epic (#3053). Third of 4 issue-* waves. - issue-2701-nul-corrupted-validators.test.cjs (9) + issue-429-comment- text-gate.test.cjs (31, incl. fast-check property tests): both target verify.cjs/validate.cjs via different calling styles (CLI vs direct- require) — merged jointly into verify.test.cjs, 0 dropped. - issue-2762-plan-reviews-chunked.test.cjs (3) merged into plan-phase-drift-guard.test.cjs. - issue-2771-advisor-subagent-type.test.cjs (1) + issue-2772-discuss- phase-text-inconsistencies.test.cjs (6) merged jointly into discuss-phase-power.test.cjs. - issue-498-update-backup-runtime-dir.test.cjs (3, rename basis) + issue-815-update-next-channel.test.cjs (7, merged in): both concern update.md workflow-text contracts, now update-workflow.test.cjs. - issue-498-update-context.test.cjs (13): pure rename to update-context.test.cjs, sole comprehensive suite for its module. - issue-2765-brace-expansion-lockfile.test.cjs (1, rename basis) + issue-3238-js-yaml-lockfile.test.cjs (1, merged in): two distinct CVE regression pins against package-lock.json, now lockfile-cve-audit.test.cjs, shared ROOT/npmLs helpers deduped instead of double-declared. Ratchet upkeep to keep this wave's own gates green: pruned 3 stale allow-test-rule allowlist entries, cited 2 previously-uncited comments that surfaced in the folded content (#3338), tightened the exemption-file ceiling 305 -> 297. Fixed one stale filename reference in production code (src/init.cts) plus one in docs/reference/workflow-fragments.md. Zero net test-coverage loss. No production code BEHAVIOR changed. * test(#3338): fix orthogonal-review findings — Wave 6 fold Standards-axis review found a real structural defect in two files, both the same root cause and both fixed here: - tests/update-workflow.test.cjs: the folded:issue-815-update-next-channel wrapper's closing brace was placed after the file's pre-existing tail instead of before it, making the already-established folded:bug-2470 and folded:bug-3130 wrappers CHILDREN of issue-815's block in the test hierarchy instead of independent siblings — confirmed via an actual node --test run showing the mislabeled TAP nesting. Moved the closing brace to the correct position; all three fold wrappers are now top-level siblings again (verified via node --test, TAP hierarchy correct, 10/10 tests, 5 suites, identical count before and after). - tests/plan-phase-drift-guard.test.cjs: same mistake in the other direction — folded:issue-2762-plan-reviews-chunked was spliced inside the pre-existing folded:bug-2492-context-coverage-gate wrapper instead of after it. Fixed the same way (227/227 tests, 38 suites, identical count before and after). - Reverted unnecessary 815-suffixed local renames (assert815/fs815/etc.) introduced by the fold — the wrapper is genuinely block-scoped once correctly closed, so no collision existed (same class as Wave 4's __foldSetNested finding). No test() count changed in either file. No production code touched. --------- Co-authored-by: sim <sim@local>
This commit is contained in:
235
tests/update-workflow.test.cjs
Normal file
235
tests/update-workflow.test.cjs
Normal file
@@ -0,0 +1,235 @@
|
||||
/**
|
||||
* Regression (#498, adversarial-review finding): the custom-file backup step in
|
||||
* update.md must derive RUNTIME_DIR from GSD_DIR.
|
||||
*
|
||||
* The get_installed_version step was rewritten to call `gsd-tools update-context`
|
||||
* and now emits GSD_DIR (the resolved config dir) instead of the old probe-loop
|
||||
* variables LOCAL_DIR / GLOBAL_DIR. The backup_custom_files step still read
|
||||
* LOCAL_DIR / GLOBAL_DIR, which are no longer assigned anywhere — so RUNTIME_DIR
|
||||
* went empty for every LOCAL/GLOBAL install and detect-custom-files was skipped.
|
||||
* Because the update then runs a clean install that wipes managed dirs
|
||||
* (commands/gsd, gsd-core), user-added files inside those dirs could be
|
||||
* deleted without the intended backup.
|
||||
*
|
||||
* This locks the fix: RUNTIME_DIR comes from GSD_DIR, and the dead LOCAL_DIR /
|
||||
* GLOBAL_DIR references are gone.
|
||||
*
|
||||
* Source-text-is-the-product: update.md's bash blocks ARE the deployed /gsd:update
|
||||
* program; asserting their shape is asserting on the deployed contract.
|
||||
*/
|
||||
|
||||
// allow-test-rule: source-text-is-the-product (#3338)
|
||||
// update.md's bash blocks ARE the deployed /gsd:update program; asserting
|
||||
// their shape is asserting on the deployed contract. The data-loss behavior
|
||||
// only manifests against a real install during a clean reinstall, which CI
|
||||
// does not perform.
|
||||
|
||||
'use strict';
|
||||
|
||||
const { describe, test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const UPDATE_MD = path.join(__dirname, '..', 'gsd-core', 'workflows', 'update.md');
|
||||
|
||||
function codeOnly(file) {
|
||||
// Strip fenced-block prose is unnecessary here; we assert on the whole doc
|
||||
// but ignore markdown comment prose by only matching shell-assignment forms.
|
||||
return fs.readFileSync(file, 'utf8');
|
||||
}
|
||||
|
||||
describe('#498 regression: update.md backup uses GSD_DIR, not the removed LOCAL_DIR/GLOBAL_DIR', () => {
|
||||
const src = codeOnly(UPDATE_MD);
|
||||
|
||||
test('RUNTIME_DIR is assigned from GSD_DIR', () => {
|
||||
assert.match(
|
||||
src,
|
||||
/RUNTIME_DIR="\$GSD_DIR"/,
|
||||
'backup_custom_files must set RUNTIME_DIR="$GSD_DIR" (the resolved config dir from update-context)',
|
||||
);
|
||||
});
|
||||
|
||||
test('no shell assignment reads the removed LOCAL_DIR/GLOBAL_DIR probe variables', () => {
|
||||
// The get_installed_version rewrite no longer assigns LOCAL_DIR/GLOBAL_DIR.
|
||||
// Any RUNTIME_DIR="$LOCAL_DIR" / "$GLOBAL_DIR" would silently resolve to empty.
|
||||
assert.doesNotMatch(
|
||||
src,
|
||||
/="\$(LOCAL_DIR|GLOBAL_DIR)"/,
|
||||
'update.md still reads LOCAL_DIR/GLOBAL_DIR, which get_installed_version no longer sets — backup will be skipped',
|
||||
);
|
||||
});
|
||||
|
||||
test('detect-custom-files stays gated on a non-empty RUNTIME_DIR', () => {
|
||||
assert.match(
|
||||
src,
|
||||
/\[ -n "\$RUNTIME_DIR" \][\s\S]*?detect-custom-files --config-dir "\$RUNTIME_DIR"/,
|
||||
'backup must still skip when RUNTIME_DIR is empty (UNKNOWN scope)',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────
|
||||
// Folded from tests/issue-815-update-next-channel.test.cjs (#3338 H3 Wave 6)
|
||||
// ────────────────────────────────────────────────────────────────────────
|
||||
{
|
||||
const { describe: __foldDescribe } = require('node:test');
|
||||
__foldDescribe('folded:issue-815-update-next-channel', () => {
|
||||
// allow-test-rule: source-text-is-the-product (#3338)
|
||||
// Reads product workflow/command markdown to verify the --next RC channel
|
||||
// contract.
|
||||
|
||||
// Issue #815: `/gsd-update --next` (alias `--rc`) must thread the @next dist-tag
|
||||
// through the whole update flow (version check + install) while leaving the
|
||||
// default @latest path unchanged.
|
||||
|
||||
const { test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const ROOT815 = path.join(__dirname, '..');
|
||||
const WF = fs.readFileSync(path.join(ROOT815, 'gsd-core', 'workflows', 'update.md'), 'utf8');
|
||||
const CMD = fs.readFileSync(path.join(ROOT815, 'commands', 'gsd', 'update.md'), 'utf8');
|
||||
|
||||
test('issue #815: workflow parses --next/--rc into a TAG channel', () => {
|
||||
assert.match(WF, /--next/);
|
||||
assert.match(WF, /--rc/);
|
||||
assert.match(WF, /TAG="next"/);
|
||||
assert.match(WF, /TAG="latest"/);
|
||||
});
|
||||
|
||||
test('issue #815: version check threads the tag through check-latest-version.cjs', () => {
|
||||
// The script path is double-quoted in the shell command, so the line is:
|
||||
// node "$GSD_DIR/gsd-core/bin/check-latest-version.cjs" --json --tag "$TAG"
|
||||
// The closing " on the script path sits between .cjs and --json.
|
||||
assert.match(WF, /check-latest-version\.cjs"? --json --tag "\$TAG"/);
|
||||
});
|
||||
|
||||
test('issue #815: install uses the selected tag, not a hardcoded @latest', () => {
|
||||
const robust = WF.match(/npx -y --package=@opengsd\/gsd-core@"\$TAG" -- gsd-core/g) || [];
|
||||
assert.ok(robust.length >= 3, `expected >=3 tag-parameterized npx invocations, found ${robust.length}`);
|
||||
assert.doesNotMatch(WF, /--package=@opengsd\/gsd-core@latest -- gsd-core/,
|
||||
'install lines must not hardcode @latest once --next exists');
|
||||
assert.doesNotMatch(WF, /--package=@opengsd\/gsd-core@(?:latest|next|beta|canary|rc) -- gsd-core/,
|
||||
'install lines must use the $TAG variable, never a hardcoded dist-tag literal');
|
||||
});
|
||||
|
||||
test('issue #815: command documents --next/--rc and routes it to the update workflow', () => {
|
||||
assert.match(CMD, /--next/);
|
||||
assert.match(CMD, /--rc/);
|
||||
assert.match(CMD, /argument-hint:.*--next/);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────
|
||||
// Folded from tests/bug-2470-update-md-claude-path.test.cjs — consolidation epic #1969 (B4 #1973)
|
||||
// ────────────────────────────────────────────────────────────────────────
|
||||
{
|
||||
const { describe: __foldDescribe } = require('node:test');
|
||||
__foldDescribe("folded:bug-2470-update-md-claude-path (consolidation epic #1969 B4 #1973)", () => {
|
||||
// allow-test-rule: source-text-is-the-product (see #2470)
|
||||
// Workflow .md / agent .md / command .md / reference .md files — their text
|
||||
// IS what the runtime loads. Testing text content tests the deployed contract.
|
||||
// Per CONTRIBUTING.md exception matrix.
|
||||
|
||||
|
||||
/**
|
||||
* Regression test for #2470.
|
||||
*
|
||||
* update.md is installed into every runtime directory including .gemini, .codex,
|
||||
* .opencode, etc. The installer's scanForLeakedPaths() uses the regex
|
||||
* /(?:~|\$HOME)\/\.claude\b/g to detect unresolved .claude path references after
|
||||
* copyWithPathReplacement() runs. The replacer handles "~/.claude/" (trailing slash)
|
||||
* but not "~/.claude" (bare, no trailing slash) — so any bare reference in
|
||||
* update.md would slip through and trigger the installer warning for non-Claude runtimes.
|
||||
*/
|
||||
|
||||
const { test: __t2470, describe: __d2470 } = require('node:test');
|
||||
const assert2470 = require('node:assert/strict');
|
||||
const fs2470 = require('fs');
|
||||
const path2470 = require('path');
|
||||
|
||||
const UPDATE_MD_2470 = path2470.join(__dirname, '..', 'gsd-core', 'workflows', 'update.md');
|
||||
|
||||
__d2470('update.md — no bare ~.claude path references (#2470)', () => {
|
||||
const content = fs2470.readFileSync(UPDATE_MD_2470, 'utf-8');
|
||||
|
||||
__t2470('update.md does not contain bare ~/\\.claude (without trailing slash)', () => {
|
||||
// This is the exact pattern from the installer's scanForLeakedPaths():
|
||||
// /(?:~|\$HOME)\/\.claude\b/g
|
||||
// The replacer handles ~/\.claude\/ (with trailing slash) but misses bare ~/\.claude
|
||||
// so we must not have bare references in the source file.
|
||||
const matches = content.match(/(?:~|\$HOME)\/\.claude(?!\/)/g);
|
||||
assert2470.strictEqual(
|
||||
matches,
|
||||
null,
|
||||
`update.md must not contain bare ~/.claude (without trailing slash) — installer scanner flags these as unresolved path refs: ${JSON.stringify(matches)}`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────
|
||||
// Folded from tests/bug-3130-update-npx-robust-invocation.test.cjs — consolidation epic #1969 (B4 #1973)
|
||||
// ────────────────────────────────────────────────────────────────────────
|
||||
{
|
||||
const { describe: __foldDescribe } = require('node:test');
|
||||
__foldDescribe("folded:bug-3130-update-npx-robust-invocation (consolidation epic #1969 B4 #1973)", () => {
|
||||
// allow-test-rule: source-text-is-the-product (see #3130)
|
||||
// Reads product workflow markdown (update.md) to verify structural
|
||||
// invocation contract.
|
||||
|
||||
// Regression guard for bug #3130.
|
||||
//
|
||||
// Two failure modes were observed with the pre-fix npx invocation form:
|
||||
// 1. Cache-stale: bare `npx -y @opengsd/gsd-core@<tag>` hits npx's local
|
||||
// cache and may pull an older version instead of the target tag.
|
||||
// 2. Token-routing: Bash-tool wrappers misroute the `@` token in
|
||||
// `@opengsd/gsd-core@<tag>`, causing npm to error with
|
||||
// "Unknown command: @opengsd/gsd-core@<tag>".
|
||||
//
|
||||
// The robust form is:
|
||||
// npx -y --package=@opengsd/gsd-core@"$TAG" -- gsd-core $ARGS
|
||||
//
|
||||
// `--package=` forces a fresh registry fetch, bypassing the npx cache.
|
||||
// `--` clearly delineates npx flags from the run-command, preventing
|
||||
// Bash-tool @-token misrouting.
|
||||
// `$TAG` is a shell variable (latest by default, next under --next/--rc),
|
||||
// set by the parse_update_channel step (#815).
|
||||
|
||||
const { test: __t3130 } = require('node:test');
|
||||
const assert3130 = require('node:assert/strict');
|
||||
const fs3130 = require('node:fs');
|
||||
const path3130 = require('node:path');
|
||||
|
||||
const ROOT_3130 = path3130.join(__dirname, '..');
|
||||
const UPDATE_WF_3130 = path3130.join(ROOT_3130, 'gsd-core', 'workflows', 'update.md');
|
||||
|
||||
const src3130 = fs3130.readFileSync(UPDATE_WF_3130, 'utf8');
|
||||
|
||||
__t3130('bug #3130: update.md contains no bare npx invocations (cache-stale form)', () => {
|
||||
// Any occurrence of `npx -y @opengsd/gsd-core@<something>` without `--package=`
|
||||
// is the stale form that triggers the two failure modes.
|
||||
const stale = (src3130.match(/npx -y @opengsd\/gsd-core@\S+[^\r\n]*/g) || []);
|
||||
assert3130.deepEqual(
|
||||
stale,
|
||||
[],
|
||||
`Stale npx forms found in update.md (must use --package= form): ${stale.join('; ')}`,
|
||||
);
|
||||
});
|
||||
|
||||
__t3130('bug #3130: update.md has >=3 robust npx invocations (--package= + -- separator)', () => {
|
||||
// Three sibling invocations: local, global, and unknown/fallback.
|
||||
// The tag is now a $TAG variable (latest by default, next under --next/--rc).
|
||||
const robust = (src3130.match(/npx -y --package=@opengsd\/gsd-core@\S+ -- gsd-core/g) || []);
|
||||
assert3130.ok(
|
||||
robust.length >= 3,
|
||||
`Expected >=3 robust npx invocations in update.md, found ${robust.length}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user