* 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>
236 lines
11 KiB
JavaScript
236 lines
11 KiB
JavaScript
/**
|
|
* 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}`,
|
|
);
|
|
});
|
|
});
|
|
}
|