Files
fission-ai__openspec/test/core/onboarding-commands.test.ts
Clay Good 3c6d318b83 fix(init): name the workflows the profile left out (#1779)
* fix(init): name the workflows the profile left out

Setup output listed the workflows it installed but never mentioned the
ones it did not, so a user on the default core profile who typed
/opsx:ff saw nothing and read it as a broken install. The docs explain
profiles; nobody reads them before typing a command that should be
there.

init now closes with the missing workflows by name and the two commands
that add them. The note is skipped when nothing was generated at all,
where the existing delivery correction is the whole story, and when the
profile already installs everything.

Also adds a troubleshooting entry for the "only some /opsx: commands
show up" symptom, which the existing list did not cover.

Relates to #1076 (the optional-workflow discoverability half; the Windows command-discovery repro in that thread is not addressed here)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* chore: drop a test scratch directory committed by mistake

test-show-command-tmp/ is created by a test run and does not exist on
main; it was picked up by a `git add -A`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(init): keep the workflow note off runs that generate nothing

With no tools selected (or only tools that could not receive a surface),
`openspec config profile` followed by `openspec update` writes nothing,
so naming the missing workflows pointed at the wrong problem.

Reported by CodeRabbit on this PR.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* refactor(init): drop the redundant update step from the workflow note

`openspec config profile` offers to apply to the current project before
it exits, and prints the `openspec update` guidance itself when the user
declines, so naming a second command was one step too many.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs(troubleshooting): match the profile steps to what the CLI does

`openspec config profile` applies to the current project itself, so
listing `openspec update` as a second required step was wrong; it is the
fallback for declining the prompt or for other projects.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(update): name the workflows the profile left out

`openspec update` is what the troubleshooting checklist tells a user to
run when a command they read about never appeared, and it is what people
run after upgrading the CLI. Neither of its existing profile notes fires
on the default `core` profile, so that user reached "All tools up to
date" and still learned nothing about the six workflows they don't have.

The note is the fallback pointer: silent when the extra-workflow or
missing-core note already named `openspec config profile`, and when no
configured tool can receive a workflow surface under the active delivery.

Reading the two existing notes as one short-circuited `||` would have
swallowed whichever ran second; they are evaluated separately.

Relates to #1076 (the optional-workflow discoverability half; the Windows command-discovery repro in that thread is not addressed here)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* refactor(update): gather the profile notes behind one call

The two call sites had grown identical six-line blocks. One
displayProfileNotes() keeps the ordering and the single-pointer rule in
one place, where the "evaluate every note, never chain them with ||"
constraint can be stated once.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs: drop the legacy troubleshooting entry

alfred-openspec on #1779: docs-lab/README.md says the old docs/ tree is legacy,
is no longer used by the site, and must stay untouched. The canonical
docs-lab/customize/profiles.md already lists the six optional workflows and the
'openspec config profile' command that adds them, and the root README already
calls out the expanded set, so this entry was a third copy in a stale tree.

The docs-lab troubleshooting page is a heading-only skeleton held back from the
site, so there is nothing to move it to; this PR is now source and tests only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 16:24:52 +00:00

88 lines
3.2 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import {
DESCRIPTION_BUDGET,
formatOptionalWorkflowsNote,
getOnboardingCommands,
} from '../../src/core/onboarding-commands.js';
import { ALL_WORKFLOWS, CORE_WORKFLOWS } from '../../src/core/profiles.js';
describe('getOnboardingCommands', () => {
it('omits commands the profile does not install', () => {
const commands = getOnboardingCommands(CORE_WORKFLOWS).map((c) => c.command);
expect(commands).toEqual(['/opsx:propose', '/opsx:apply']);
expect(commands).not.toContain('/opsx:new');
expect(commands).not.toContain('/opsx:continue');
});
it('includes expanded commands when a custom profile installs them', () => {
const commands = getOnboardingCommands(['new', 'continue', 'apply']).map((c) => c.command);
expect(commands).toEqual(['/opsx:new', '/opsx:continue', '/opsx:apply']);
});
it('returns lifecycle order regardless of the order workflows are given', () => {
const commands = getOnboardingCommands(['apply', 'continue', 'propose']).map((c) => c.command);
expect(commands).toEqual(['/opsx:propose', '/opsx:continue', '/opsx:apply']);
});
it('returns nothing when no onboarding workflow is installed', () => {
expect(getOnboardingCommands(['archive', 'sync'])).toEqual([]);
expect(getOnboardingCommands([])).toEqual([]);
});
it('keeps descriptions within the welcome screen width budget', () => {
// A longer description wraps the welcome screen at 60 columns, which desyncs
// its animation. See the width test in test/ui/welcome-screen.test.ts.
for (const { command, description } of getOnboardingCommands(ALL_WORKFLOWS)) {
expect(description.length, `${command} description is too long`).toBeLessThanOrEqual(
DESCRIPTION_BUDGET
);
}
});
});
describe('formatOptionalWorkflowsNote', () => {
it('names every workflow the core profile leaves out', () => {
const note = formatOptionalWorkflowsNote(CORE_WORKFLOWS);
expect(note).not.toBeNull();
expect(note?.[0]).toBe(
'Note: 6 more workflows are available (new, continue, ff, bulk-archive, verify, onboard).'
);
expect(note?.[1]).toBe(
'Add them with `openspec config profile`.'
);
});
it('lists the missing workflows in declaration order, not the order given', () => {
const installed = ALL_WORKFLOWS.filter(
(workflow) => workflow !== 'new' && workflow !== 'verify'
);
const note = formatOptionalWorkflowsNote([...installed].reverse());
expect(note?.[0]).toBe('Note: 2 more workflows are available (new, verify).');
});
it('reads as a singular sentence when exactly one workflow is missing', () => {
const note = formatOptionalWorkflowsNote(
ALL_WORKFLOWS.filter((workflow) => workflow !== 'onboard')
);
expect(note?.[0]).toBe('Note: 1 more workflow is available (onboard).');
expect(note?.[1]).toBe(
'Add it with `openspec config profile`.'
);
});
it('returns null when every workflow is installed', () => {
expect(formatOptionalWorkflowsNote(ALL_WORKFLOWS)).toBeNull();
});
it('ignores workflow names that are not part of the system', () => {
expect(formatOptionalWorkflowsNote([...ALL_WORKFLOWS, 'not-a-workflow'])).toBeNull();
});
});