feat: add list/status/resume/close subcommands to /gsd-quick and /gsd-thread (#2159)

* feat(2155): add list/status/resume subcommands and security hardening to /gsd-quick

- Add SUBCMD routing (list/status/resume/run) before quick workflow delegation
- LIST subcommand scans .planning/quick/ dirs, reads SUMMARY.md frontmatter status
- STATUS subcommand shows plan description and current status for a slug
- RESUME subcommand finds task by slug, prints context, then resumes quick workflow
- Slug sanitization: only [a-z0-9-], max 60 chars, reject ".." and "/"
- Directory name sanitization for display (strip non-printable + ANSI sequences)
- Add security_notes section documenting all input handling guarantees

* feat(2156): formalize thread status frontmatter, add list/close/status subcommands, remove heredoc injection risk

- Replace heredoc (cat << 'EOF') with Write tool instruction — eliminates shell injection risk
- Thread template now uses YAML frontmatter (slug, title, status, created, updated fields)
- Add subcommand routing: list / list --open / list --resolved / close <slug> / status <slug>
- LIST mode reads status from frontmatter, falls back to ## Status heading
- CLOSE mode updates frontmatter status to resolved via frontmatter set, then commits
- STATUS mode displays thread summary (title, status, goal, next steps) without spawning
- RESUME mode updates status from open → in_progress via frontmatter set
- Slug sanitization for close/status: only [a-z0-9-], max 60 chars, reject ".." and "/"
- Add security_notes section documenting all input handling guarantees

* test(2155,2156): add quick and thread session management tests

- quick-session-management.test.cjs: verifies list/status/resume routing,
  slug sanitization, directory sanitization, frontmatter get usage, security_notes
- thread-session-management.test.cjs: verifies list filters (--open/--resolved),
  close/status subcommands, no heredoc, frontmatter fields, Write tool usage,
  slug sanitization, security_notes
This commit is contained in:
Tom Boucher
2026-04-12 10:05:17 -04:00
committed by GitHub
parent 1aa89b8ae2
commit 7b07dde150
4 changed files with 440 additions and 40 deletions

View File

@@ -0,0 +1,71 @@
'use strict';
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
describe('quick session management (#2155)', () => {
const quickCmd = fs.readFileSync(
path.join(__dirname, '..', 'commands', 'gsd', 'quick.md'),
'utf8'
);
test('quick command has list subcommand', () => {
assert.ok(quickCmd.includes('SUBCMD=list'), 'missing list subcommand routing');
});
test('quick command has status subcommand', () => {
assert.ok(quickCmd.includes('SUBCMD=status'), 'missing status subcommand routing');
});
test('quick command has resume subcommand', () => {
assert.ok(quickCmd.includes('SUBCMD=resume'), 'missing resume subcommand routing');
});
test('quick command has slug sanitization', () => {
assert.ok(
quickCmd.includes('sanitiz') || quickCmd.includes('[a-z0-9'),
'missing slug sanitization'
);
});
test('quick command has security_notes section', () => {
assert.ok(quickCmd.includes('security_notes'), 'missing security_notes section');
});
test('quick command list subcommand stops after display', () => {
assert.ok(
quickCmd.includes('STOP after displaying the list'),
'list subcommand should stop after display'
);
});
test('quick command rejects slugs with path traversal', () => {
assert.ok(
quickCmd.includes('..') && quickCmd.includes('reject'),
'missing path traversal rejection for slugs'
);
});
test('quick command sanitizes directory names for display', () => {
assert.ok(
quickCmd.includes('non-printable') || quickCmd.includes('ANSI'),
'missing directory name sanitization for display'
);
});
test('quick command list uses frontmatter get for status', () => {
assert.ok(
quickCmd.includes('frontmatter get'),
'list should use frontmatter get to read status'
);
});
test('quick command shows complete checkmark in list', () => {
assert.ok(
quickCmd.includes('complete ✓') || quickCmd.includes('complete'),
'list should show complete status'
);
});
});