- Add commandsList handler — returns sorted JSON array of all registered verb strings; satisfies workstream-flag.md + agent tooling discoverability - Register ['commands', commandsList] in DECISION_ROUTING_STATIC_CATALOG - Add golden-policy exemption (SDK-only, no CJS mirror needed) - check.decision-coverage-plan/verify were already registered; commands was the remaining gap Closes #3121
This commit is contained in:
5
.changeset/fix-3121-gsd-tools-commands-verb.md
Normal file
5
.changeset/fix-3121-gsd-tools-commands-verb.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3121
|
||||
---
|
||||
**`gsd-sdk query commands` no longer returns "Unknown command"** — `commands` was referenced in `references/workstream-flag.md` and by agent tooling for verb discovery but had no SDK handler. A new `commandsList` handler in the native registry returns a sorted JSON array of all registered verb strings. `check.decision-coverage-plan` and `check.decision-coverage-verify` were already registered in the SDK native registry; the remaining gap was the `commands` introspection verb. Closes #3121.
|
||||
@@ -51,6 +51,8 @@ const NO_CJS_SUBPROCESS_REASON: Record<string, string> = {
|
||||
'SDK-only structured plan parse (no CJS mirror). Covered in sdk/src/query/plan-task-structure.test.ts.',
|
||||
'requirements.extract-from-plans':
|
||||
'SDK-only requirements aggregation (no CJS mirror). Covered in sdk/src/query/requirements-extract-from-plans.test.ts.',
|
||||
'commands':
|
||||
'SDK-only registry introspection (no gsd-tools.cjs equivalent — the CJS layer has no self-describing verb). Covered in sdk/src/query/commands-list.test.ts. Closes #3121.',
|
||||
};
|
||||
|
||||
const READ_HANDLER_ONLY_REASON = (cmd: string) =>
|
||||
|
||||
@@ -14,6 +14,7 @@ import { templateFill, templateSelect } from './template.js';
|
||||
import { verifySummary, verifyPathExists } from './verify.js';
|
||||
import { decisionsParse } from './decisions.js';
|
||||
import { checkDecisionCoveragePlan, checkDecisionCoverageVerify } from './check-decision-coverage.js';
|
||||
import { commandsList } from './commands-list.js';
|
||||
import { checkConfigGates } from './config-gates.js';
|
||||
import { checkAutoMode } from './check-auto-mode.js';
|
||||
import { checkPhaseReady } from './phase-ready.js';
|
||||
@@ -95,4 +96,5 @@ export const DECISION_ROUTING_STATIC_CATALOG: ReadonlyArray<readonly [string, Qu
|
||||
['check verification-status', checkVerificationStatus],
|
||||
['check.ship-ready', checkShipReady],
|
||||
['check ship-ready', checkShipReady],
|
||||
['commands', commandsList],
|
||||
] as const;
|
||||
|
||||
36
sdk/src/query/commands-list.test.ts
Normal file
36
sdk/src/query/commands-list.test.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { commandsList } from './commands-list.js';
|
||||
|
||||
// Regression test for bug #3121.
|
||||
// The `commands` verb was missing from the SDK native registry.
|
||||
// `gsd-sdk query commands` fell back to gsd-tools.cjs which threw
|
||||
// "Unknown command: commands".
|
||||
describe('commands-list handler (#3121)', () => {
|
||||
it('returns a non-empty sorted JSON array of command strings', async () => {
|
||||
const result = await commandsList([], '/tmp', undefined);
|
||||
expect(result.data).toBeDefined();
|
||||
expect(Array.isArray(result.data)).toBe(true);
|
||||
expect((result.data as string[]).length).toBeGreaterThan(10);
|
||||
});
|
||||
|
||||
it('includes known canonical commands', async () => {
|
||||
const result = await commandsList([], '/tmp', undefined);
|
||||
const cmds = result.data as string[];
|
||||
expect(cmds).toContain('state.begin-phase');
|
||||
expect(cmds).toContain('check.decision-coverage-plan');
|
||||
expect(cmds).toContain('commands');
|
||||
});
|
||||
|
||||
it('is sorted alphabetically', async () => {
|
||||
const result = await commandsList([], '/tmp', undefined);
|
||||
const cmds = result.data as string[];
|
||||
const sorted = [...cmds].sort((a, b) => a.localeCompare(b));
|
||||
expect(cmds).toEqual(sorted);
|
||||
});
|
||||
|
||||
it('args and workstream are ignored (introspection verb)', async () => {
|
||||
const r1 = await commandsList([], '/tmp', undefined);
|
||||
const r2 = await commandsList(['ignored'], '/other', 'ws');
|
||||
expect(r1.data).toEqual(r2.data);
|
||||
});
|
||||
});
|
||||
19
sdk/src/query/commands-list.ts
Normal file
19
sdk/src/query/commands-list.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import type { QueryHandler } from './utils.js';
|
||||
import { createRegistry } from './index.js';
|
||||
|
||||
/**
|
||||
* `commands` — return the full list of registered query command strings.
|
||||
*
|
||||
* Closes #3121: the `commands` verb was referenced in workflow files
|
||||
* (references/workstream-flag.md) but had no native SDK handler, causing
|
||||
* a fallback to gsd-tools.cjs which threw "Unknown command: commands".
|
||||
*
|
||||
* Returns: JSON array of all canonical + alias command strings the SDK
|
||||
* registry accepts, sorted alphabetically. Suitable for discoverability
|
||||
* and for agent auto-complete when constructing `gsd-sdk query` calls.
|
||||
*/
|
||||
export const commandsList: QueryHandler<string[]> = async (_args, _projectDir) => {
|
||||
const registry = createRegistry();
|
||||
const cmds = registry.commands().sort((a, b) => a.localeCompare(b));
|
||||
return { data: cmds };
|
||||
};
|
||||
Reference in New Issue
Block a user