mirror of
https://github.com/nexu-io/open-design.git
synced 2026-09-20 06:15:06 +08:00
f1a0b60c6c
* chore: unify product name display to OpenDesign repo-wide Rename the user-facing product name from "Open Design" to "OpenDesign" across documentation, web/desktop UI strings, i18n locales, prompts, plugin/skill/design-system content, changelogs, and specs, matching the landing-page rename already in flight. Deliberately excluded so installed-app identity stays untouched: - packages/release PRODUCT_NAME and everything derived from it (app bundle names, installer/registry identity, updater feeds) - tools/pack, tools/release, apps/packaged, landing page (PR #6983) - mocks/ recorded traces and LICENSE - test fixtures and comments that encode real on-disk install paths (Open Design.app, Program Files\Open Design, AppData paths) Validated with pnpm guard, pnpm typecheck, and targeted web/desktop/host test runs over the renamed strings. * fix(web): keep two-letter author monogram for CamelCase brand names authorInitials split words on whitespace only, so the renamed single-token author name "OpenDesign" produced a one-letter monogram. Treat lower-to-upper CamelCase boundaries as word breaks so the avatar fallback stays "OD". * fix(release): restore spaced Windows source artifact names in win.ps1 tools/pack still derives builder outputs from the unchanged PRODUCT_NAME ("Open Design"), so the asset script must look for the spaced filenames. Matches the mac/mac-intel/linux asset scripts, which were already kept on the original names. --------- Co-authored-by: Joey <236967869+joeylee12629-star@users.noreply.github.com>
128 lines
5.9 KiB
TypeScript
128 lines
5.9 KiB
TypeScript
// Contract test for the prompts the plugin-folder card buttons send to the
|
|
// agent. `install` uses the simple shared template; `contribute` drives the
|
|
// `gh repo fork → branch → commit → gh pr create --web` flow against
|
|
// `nexu-io/open-design`; `publish` drives `gh repo create / push` against the
|
|
// author's own `plugin.repo` URL. The tests below lock the *shape* of each
|
|
// prompt (keywords + folder interpolation) without coupling to exact wording,
|
|
// so prose tweaks don't break the suite but accidental removal of a critical
|
|
// step would.
|
|
|
|
import { describe, expect, it } from 'vitest';
|
|
import { buildPluginFolderAgentActionPrompt } from '../../src/components/design-files/pluginFolderActions';
|
|
|
|
const FOLDER = 'generated-plugin';
|
|
|
|
describe('buildPluginFolderAgentActionPrompt', () => {
|
|
describe('install', () => {
|
|
it('mentions the folder path and the supported install CLI', () => {
|
|
const prompt = buildPluginFolderAgentActionPrompt(FOLDER, 'install');
|
|
expect(prompt).toContain(`Plugin folder: \`${FOLDER}\``);
|
|
expect(prompt).toContain('od plugin install --source');
|
|
});
|
|
});
|
|
|
|
describe('publish (repo-publish flow)', () => {
|
|
const prompt = buildPluginFolderAgentActionPrompt(FOLDER, 'publish');
|
|
|
|
it('delegates repo publishing to the deterministic plugin CLI helper', () => {
|
|
expect(prompt).toContain(`Plugin folder: \`${FOLDER}\``);
|
|
expect(prompt).toContain(`"$OD_NODE_BIN" "$OD_BIN" plugin publish-repo ${FOLDER}`);
|
|
expect(prompt).toMatch(/current gh login|target is not hard-coded/i);
|
|
});
|
|
|
|
it('states the CLI-owned responsibilities instead of re-listing shell steps', () => {
|
|
expect(prompt).toContain('gh api user --jq .login');
|
|
expect(prompt).toContain('--owner');
|
|
expect(prompt).toMatch(/last-resort fallback/i);
|
|
expect(prompt).toMatch(/placeholder owners are rejected/i);
|
|
expect(prompt).toMatch(/manifest repo normalization/i);
|
|
expect(prompt).toMatch(/repo existence check/i);
|
|
expect(prompt).toMatch(/repo create\/update/i);
|
|
expect(prompt).toMatch(/final verification/i);
|
|
});
|
|
|
|
it('bans the registry-submission CLI explicitly', () => {
|
|
// The legacy CLI is what shipped the bug — without an explicit ban
|
|
// the agent had been routing back to it. The mention must be in a
|
|
// negative imperative ("Do NOT call …"), not a recommendation.
|
|
expect(prompt).toMatch(
|
|
/Do NOT (call|route through) `?od plugin publish --to open-design`?/i,
|
|
);
|
|
expect(prompt).toMatch(
|
|
/registry[- ]submission|registry-submission flow|OpenDesign PR/i,
|
|
);
|
|
});
|
|
|
|
it('hard-bans mid-turn clarification + auto-install + force-push + retry', () => {
|
|
expect(prompt).toContain('<question-form>');
|
|
expect(prompt).toMatch(/fire-and-forget|clarification UI that waits/i);
|
|
expect(prompt).toMatch(/do not try to install/i);
|
|
expect(prompt).toMatch(/do not force-push|--force/i);
|
|
expect(prompt).toMatch(/do not retry/i);
|
|
});
|
|
|
|
it('interpolates the actual folder path into the CLI command', () => {
|
|
// Sanity check that template-string interpolation didn't regress into
|
|
// literal `${folderPath}` substrings.
|
|
expect(prompt).toContain(`plugin publish-repo ${FOLDER}`);
|
|
expect(prompt).not.toContain('${folderPath}');
|
|
});
|
|
|
|
it('ends by handing the repo URL back to chat', () => {
|
|
expect(prompt).toMatch(/final repo URL printed by the CLI/i);
|
|
});
|
|
});
|
|
|
|
describe('contribute (PR-based flow)', () => {
|
|
const prompt = buildPluginFolderAgentActionPrompt(FOLDER, 'contribute');
|
|
|
|
it('delegates OpenDesign PR creation to the deterministic plugin CLI helper', () => {
|
|
expect(prompt).toContain('nexu-io/open-design');
|
|
expect(prompt).toContain(`"$OD_NODE_BIN" "$OD_BIN" plugin open-design-pr ${FOLDER}`);
|
|
});
|
|
|
|
it('states the CLI-owned PR workflow instead of re-listing shell steps', () => {
|
|
expect(prompt).toContain('gh api user --jq .login');
|
|
expect(prompt).toContain('--owner');
|
|
expect(prompt).toMatch(/last-resort fallback/i);
|
|
expect(prompt).toMatch(/fork\/clone\/copy\/branch\/push/i);
|
|
expect(prompt).toContain('gh pr create --web');
|
|
// The legacy CLI is named in the prompt only as part of an explicit
|
|
// ban ("Do NOT call the legacy `od plugin publish --to open-design`")
|
|
// — verify the ban is in place, not the bare command.
|
|
expect(prompt).toMatch(/do not call the legacy `od plugin publish --to open-design`/i);
|
|
});
|
|
|
|
it('uses --web so the author confirms the PR in browser', () => {
|
|
// The "author keeps the final review click" invariant — preserved from
|
|
// 45f52d71's "We never POST anywhere" principle.
|
|
expect(prompt).toContain('--web');
|
|
expect(prompt).toMatch(/do not auto-submit/i);
|
|
});
|
|
|
|
it('hard-bans mid-turn clarification forms to avoid 600s stalls', () => {
|
|
// Regression guard for the stall we observed during e2e: agent paused
|
|
// mid-turn on a clarification waiting for a host answer the user never
|
|
// sent (they clicked the plugin-folder card instead).
|
|
expect(prompt).toContain('<question-form>');
|
|
expect(prompt).toMatch(/clarification UI that waits|fire-and-forget/i);
|
|
});
|
|
|
|
it('forbids the agent from installing tools or retrying failures', () => {
|
|
expect(prompt).toMatch(/do not try to install/i);
|
|
expect(prompt).toMatch(/do not retry/i);
|
|
});
|
|
|
|
it('interpolates the actual folder path into manifest and copy steps', () => {
|
|
// Sanity check that template-string interpolation didn't regress into
|
|
// literal `${folderPath}` substrings (we already shipped that bug once).
|
|
expect(prompt).toContain(`plugin open-design-pr ${FOLDER}`);
|
|
expect(prompt).not.toContain('${folderPath}');
|
|
});
|
|
|
|
it('ends by handing the PR URL back to chat', () => {
|
|
expect(prompt).toMatch(/PR URL|pull\/new|paste it into chat/);
|
|
});
|
|
});
|
|
});
|