* enhancement(#558): add liveness hints to all GSD spawn announcements Append '(runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)' inline to every ◆ Spawning… banner and subagent dispatch instruction across 26 workflows. Silent subagents look identical to frozen sessions — this note sets the expectation so users wait instead of killing healthy in-progress work. Changes: - references/ui-brand.md: document liveness convention under Spawning Indicators - 10 banner workflows: append liveness note to ◆ Spawning… lines in-place - 18 subagent-only workflows: add print instruction with liveness phrase - tests/spawn-liveness-banner.test.cjs: new test; fails if any workflow with subagent_type omits 'runs in a subagent' - docs/USER-GUIDE.md: troubleshooting entry for frozen-looking spawns - .changeset/558-spawn-liveness-banner.md: changeset fragment Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(#558): add missing pr field to changeset fragment The changeset lint requires pr: <NNN> in frontmatter; the fragment was written without it, causing parse.cjs to reject it. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(#558): address codex review — missed spawns and tighten test - plan-phase.md: add liveness note to chunked outline planner and per-plan chunked planner banners (two missed ◆ Spawning… lines) - quick.md: add liveness note to research banner and add missing display line before planner spawn in Step 5 - plan-review-convergence.md: add liveness note to initial planning and review-agent spawn Display lines - docs-update.md: add Print instructions with liveness note before gsd-doc-verifier spawns in Phase 1 and Phase 2 - autonomous.md: add Print instruction with liveness note before background plan-phase agent dispatch in step 3b - tests/spawn-liveness-banner.test.cjs: replace single file-level check with two assertions: (1) every ◆ Spawning… banner line carries the phrase on that line (2) every file with subagent_type contains the phrase somewhere The tighter test would have caught all five missed spawns. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(#558): tighten spawn-liveness test regex to catch spawn-word-anywhere variants Previous SPAWN_BANNER_RE only matched ◆ immediately followed by Spawning|spawning. Replace with /◆[^\n]*\bspawning?\b/i which matches the spawn word anywhere on the ◆ line — catching "◆ Chunked mode: spawning outline planner..." and similar. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(#558): rename changeset to PR number 566 and correct pr field Changeset was filed as 558-spawn-liveness-banner.md (issue#) but the convention is the PR number. Renamed to 566-spawn-liveness-banner.md and updated pr: 558 → pr: 566 so release notes link to the right PR. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
111 lines
4.8 KiB
JavaScript
111 lines
4.8 KiB
JavaScript
// allow-test-rule: source-text-is-the-product
|
|
// Tests that every GSD workflow that spawns a subagent carries the liveness phrase
|
|
// "runs in a subagent" on its spawn announcement lines.
|
|
// Canonical phrase defined in get-shit-done/references/ui-brand.md § Spawning Indicators.
|
|
// Regression test for https://github.com/open-gsd/gsd-core/issues/558.
|
|
//
|
|
// TEST STRATEGY: Two complementary assertions.
|
|
//
|
|
// 1. SPAWN-BANNER CHECK — any ◆ display line that contains the word "spawn" or "spawning"
|
|
// (case-insensitive, anywhere on the line) must carry the liveness phrase. This is more
|
|
// rigorous than a simple /◆\s+[Ss]pawning/ prefix match and catches all variants:
|
|
// "◆ Spawning researcher..." (spawn word right after ◆)
|
|
// "◆ Chunked mode: spawning planner..." (spawn word after prefix text)
|
|
// It does NOT match ◆ status/error lines that say "planner returned" or "checker wrote"
|
|
// (those don't contain the word "spawn"), so it has no false positives.
|
|
//
|
|
// 2. PRESENCE CHECK (coarser fallback) — if a file contains `subagent_type`, it must
|
|
// contain the liveness phrase at least once. Catches workflows that dispatch subagents
|
|
// without any ◆ spawn banner (e.g. prose-only "Spawn X" instructions).
|
|
//
|
|
// Together the two assertions are strictly stronger than the original file-level check:
|
|
// a file with 10 spawns where only one ◆ Spawning banner got the note still fails #1.
|
|
// A file with subagent_type but no banner at all still fails #2.
|
|
|
|
const { test, describe } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const WORKFLOWS_DIR = path.join(__dirname, '..', 'get-shit-done', 'workflows');
|
|
const LIVENESS_PHRASE = 'runs in a subagent';
|
|
|
|
// Matches any ◆ line that contains "spawn" or "spawning" (case-insensitive, word anywhere).
|
|
// This covers:
|
|
// "◆ Spawning researcher..." → matched
|
|
// "◆ Chunked mode: spawning X..." → matched
|
|
// But NOT:
|
|
// "◆ Planner wrote N plan(s)..." → not matched (no "spawn" word)
|
|
// "◆ Research phase enabled" → not matched (no "spawn" word)
|
|
const SPAWN_BANNER_RE = /◆[^\n]*\bspawning?\b/i;
|
|
|
|
function findMdFiles(dir) {
|
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
const files = [];
|
|
for (const entry of entries) {
|
|
const full = path.join(dir, entry.name);
|
|
if (entry.isDirectory()) {
|
|
files.push(...findMdFiles(full));
|
|
} else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
files.push(full);
|
|
}
|
|
}
|
|
return files;
|
|
}
|
|
|
|
describe('spawn-liveness-banner', () => {
|
|
test('every ◆ spawn announcement line carries the liveness phrase "runs in a subagent"', () => {
|
|
const mdFiles = findMdFiles(WORKFLOWS_DIR);
|
|
const bannerViolations = [];
|
|
|
|
for (const filePath of mdFiles) {
|
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
const lines = content.split('\n');
|
|
const rel = path.relative(WORKFLOWS_DIR, filePath);
|
|
|
|
for (let i = 0; i < lines.length; i++) {
|
|
const line = lines[i];
|
|
if (SPAWN_BANNER_RE.test(line) && !line.includes(LIVENESS_PHRASE)) {
|
|
bannerViolations.push(`${rel}:${i + 1}: ${line.trim()}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
assert.deepStrictEqual(
|
|
bannerViolations,
|
|
[],
|
|
`The following ◆ spawn announcement lines are missing the liveness phrase "${LIVENESS_PHRASE}":\n` +
|
|
bannerViolations.map(v => ` - ${v}`).join('\n') +
|
|
'\n\nPer get-shit-done/references/ui-brand.md § "Spawning Indicators":\n' +
|
|
'every ◆ spawn announcement must carry "runs in a subagent" so users know\n' +
|
|
'that silence during a subagent run is expected and do not kill a healthy agent.\n' +
|
|
'See https://github.com/open-gsd/gsd-core/issues/558'
|
|
);
|
|
});
|
|
|
|
test('every workflow that dispatches a subagent contains the liveness phrase somewhere', () => {
|
|
const mdFiles = findMdFiles(WORKFLOWS_DIR);
|
|
const presenceViolations = [];
|
|
|
|
for (const filePath of mdFiles) {
|
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
const rel = path.relative(WORKFLOWS_DIR, filePath);
|
|
|
|
if (content.includes('subagent_type') && !content.includes(LIVENESS_PHRASE)) {
|
|
presenceViolations.push(rel);
|
|
}
|
|
}
|
|
|
|
assert.deepStrictEqual(
|
|
presenceViolations,
|
|
[],
|
|
`The following workflow files contain "subagent_type" but are missing the liveness phrase "${LIVENESS_PHRASE}" anywhere in the file:\n` +
|
|
presenceViolations.map(f => ` - ${f}`).join('\n') +
|
|
'\n\nPer get-shit-done/references/ui-brand.md § "Spawning Indicators":\n' +
|
|
'every workflow that spawns a subagent must carry "runs in a subagent" so users know\n' +
|
|
'that silence during a subagent run is expected and do not kill a healthy agent.\n' +
|
|
'See https://github.com/open-gsd/gsd-core/issues/558'
|
|
);
|
|
});
|
|
});
|