@bastani/atomic 0.9.0-alpha.2 → 0.9.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/dist/builtin/cursor/package.json +2 -2
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +24 -0
- package/dist/builtin/workflows/README.md +12 -12
- package/dist/builtin/workflows/builtin/goal-ledger.ts +2 -0
- package/dist/builtin/workflows/builtin/goal-prompts.ts +8 -0
- package/dist/builtin/workflows/builtin/goal-reports.ts +5 -0
- package/dist/builtin/workflows/builtin/goal-runner.ts +103 -4
- package/dist/builtin/workflows/builtin/goal-types.ts +4 -0
- package/dist/builtin/workflows/builtin/goal.d.ts +4 -0
- package/dist/builtin/workflows/builtin/goal.ts +14 -2
- package/dist/builtin/workflows/builtin/index.d.ts +8 -8
- package/dist/builtin/workflows/builtin/open-claude-design-feedback.ts +359 -0
- package/dist/builtin/workflows/builtin/open-claude-design-phases.ts +254 -352
- package/dist/builtin/workflows/builtin/open-claude-design-runner.ts +256 -414
- package/dist/builtin/workflows/builtin/open-claude-design-setup.ts +272 -0
- package/dist/builtin/workflows/builtin/open-claude-design-utils.ts +58 -68
- package/dist/builtin/workflows/builtin/open-claude-design.d.ts +5 -9
- package/dist/builtin/workflows/builtin/open-claude-design.ts +14 -26
- package/dist/builtin/workflows/builtin/prompt-refinement.ts +102 -0
- package/dist/builtin/workflows/builtin/ralph-core.ts +6 -4
- package/dist/builtin/workflows/builtin/ralph-runner.ts +22 -24
- package/dist/builtin/workflows/builtin/ralph.d.ts +2 -0
- package/dist/builtin/workflows/builtin/ralph.ts +3 -1
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/skills/impeccable/SKILL.md +14 -23
- package/dist/builtin/workflows/skills/impeccable/reference/brand.md +2 -2
- package/dist/builtin/workflows/skills/impeccable/reference/live.md +25 -4
- package/dist/builtin/workflows/skills/impeccable/scripts/context-signals.mjs +1 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/context.mjs +724 -29
- package/dist/builtin/workflows/skills/impeccable/scripts/critique-storage.mjs +1 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/browser/injected/index.mjs +219 -7
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/cli/main.mjs +57 -11
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/design-system.mjs +750 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +648 -53
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/detect-antipatterns.mjs +7 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/browser/detect-url.mjs +29 -4
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +44 -11
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +29 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/static-html/detect-html.mjs +27 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/node/file-system.mjs +1 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/registry/antipatterns.mjs +29 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/rules/checks.mjs +401 -46
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/shared/inline-ignores.mjs +148 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/shared/page.mjs +6 -6
- package/dist/builtin/workflows/skills/impeccable/scripts/{design-parser.mjs → lib/design-parser.mjs} +8 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/impeccable-config.mjs +638 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/impeccable-paths.mjs +128 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/{is-generated.mjs → lib/is-generated.mjs} +2 -2
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/target-args.mjs +42 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/browser-script-parts.mjs +49 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/{live-completion.mjs → live/completion.mjs} +1 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/{live-event-validation.mjs → live/event-validation.mjs} +6 -5
- package/dist/builtin/workflows/skills/impeccable/scripts/live/manual-apply.mjs +939 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/manual-edit-routes.mjs +357 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/{live-manual-edits-buffer.mjs → live/manual-edits-buffer.mjs} +1 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/{live-session-store.mjs → live/session-store.mjs} +21 -3
- package/dist/builtin/workflows/skills/impeccable/scripts/live/svelte-component.mjs +835 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/sveltekit-adapter.mjs +274 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/ui-core.mjs +180 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/vocabulary.mjs +36 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live-accept.mjs +185 -60
- package/dist/builtin/workflows/skills/impeccable/scripts/live-browser-dom.js +146 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live-browser.js +3369 -1026
- package/dist/builtin/workflows/skills/impeccable/scripts/live-commit-manual-edits.mjs +2 -2
- package/dist/builtin/workflows/skills/impeccable/scripts/live-complete.mjs +2 -2
- package/dist/builtin/workflows/skills/impeccable/scripts/live-discard-manual-edits.mjs +1 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/live-inject.mjs +133 -9
- package/dist/builtin/workflows/skills/impeccable/scripts/live-insert.mjs +42 -2
- package/dist/builtin/workflows/skills/impeccable/scripts/live-manual-edit-evidence.mjs +4 -4
- package/dist/builtin/workflows/skills/impeccable/scripts/live-poll.mjs +21 -15
- package/dist/builtin/workflows/skills/impeccable/scripts/live-resume.mjs +1 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/live-server.mjs +205 -1269
- package/dist/builtin/workflows/skills/impeccable/scripts/live-status.mjs +2 -2
- package/dist/builtin/workflows/skills/impeccable/scripts/live-target.mjs +30 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live-wrap.mjs +69 -26
- package/dist/builtin/workflows/skills/impeccable/scripts/live.mjs +73 -22
- package/dist/builtin/workflows/src/extension/workflow-prompts.ts +3 -1
- package/dist/core/atomic-guide-command.d.ts.map +1 -1
- package/dist/core/atomic-guide-command.js +5 -5
- package/dist/core/atomic-guide-command.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +0 -1
- package/dist/core/system-prompt.js.map +1 -1
- package/docs/index.md +2 -2
- package/docs/quickstart.md +9 -9
- package/docs/workflows.md +816 -47
- package/package.json +2 -2
- package/dist/builtin/workflows/skills/impeccable/scripts/cleanup-deprecated.mjs +0 -284
- package/dist/builtin/workflows/skills/impeccable/scripts/impeccable-paths.mjs +0 -126
- /package/dist/builtin/workflows/skills/impeccable/scripts/{live-insert-ui.mjs → live/insert-ui.mjs} +0 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* open-claude-design setup helpers.
|
|
3
|
+
*
|
|
4
|
+
* Capabilities that delegate to the accessible `impeccable` skill
|
|
5
|
+
* (`/skill:impeccable …`), factored into this module so the runner and phases
|
|
6
|
+
* files stay under the 500-line file-length gate:
|
|
7
|
+
*
|
|
8
|
+
* 1. Discovery + init front door: one `discovery` stage runs
|
|
9
|
+
* `/skill:impeccable shape` and `/skill:impeccable init`, interviews the
|
|
10
|
+
* user for the design brief/output type/references, then lets impeccable
|
|
11
|
+
* detect/create/reconcile PRODUCT.md and DESIGN.md in the same stage.
|
|
12
|
+
* 2. Reference discovery: browse five curated galleries (Awwwards,
|
|
13
|
+
* recent.design, Dribbble, Monet, Motionsites) and synthesize a references
|
|
14
|
+
* brief the generator heavily emulates.
|
|
15
|
+
* 3. Live interactive QA prompt: drive `/skill:impeccable live` against the
|
|
16
|
+
* static preview.html so the user picks elements, annotates, and accepts
|
|
17
|
+
* on-brand variants in the browser. cross-ref: impeccable `reference/live.md`.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
21
|
+
import { join } from "node:path";
|
|
22
|
+
import type { WorkflowTaskResult } from "../src/shared/types.js";
|
|
23
|
+
import {
|
|
24
|
+
OUTPUT_TYPES,
|
|
25
|
+
discoveryDecisionFromResult,
|
|
26
|
+
taggedPrompt,
|
|
27
|
+
type DiscoveryDecision,
|
|
28
|
+
} from "./open-claude-design-utils.js";
|
|
29
|
+
|
|
30
|
+
type SetupModelConfig = Record<string, object | string | readonly string[]>;
|
|
31
|
+
type SetupDesignContext = {
|
|
32
|
+
task(name: string, options: object): Promise<WorkflowTaskResult>;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// 0. Discovery + init front door (one workflow stage)
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
export type ProjectDesignContextResult = {
|
|
40
|
+
readonly summary: string;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export function renderDiscoveryContext(discovery: DiscoveryDecision): string {
|
|
44
|
+
return [
|
|
45
|
+
`Confirmed design brief: ${discovery.brief}`,
|
|
46
|
+
`Output type: ${discovery.output_type}`,
|
|
47
|
+
discovery.references.length > 0
|
|
48
|
+
? `References to emulate (take precedence over DESIGN.md/PRODUCT.md): ${discovery.references.join(", ")}`
|
|
49
|
+
: "References to emulate: none provided.",
|
|
50
|
+
].join("\n");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function buildDiscoveryInitPrompt(prompt: string): string {
|
|
54
|
+
const outputTypes = OUTPUT_TYPES.join(", ");
|
|
55
|
+
return `/skill:impeccable shape
|
|
56
|
+
/skill:impeccable init
|
|
57
|
+
|
|
58
|
+
${taggedPrompt([
|
|
59
|
+
[
|
|
60
|
+
"role",
|
|
61
|
+
"You are an opinionated staff designer running the open-claude-design front door.",
|
|
62
|
+
],
|
|
63
|
+
[
|
|
64
|
+
"objective",
|
|
65
|
+
`In ONE workflow stage, first shape the request into a confirmed design brief, output type, and reference list for: ${prompt}. Then immediately run impeccable's \`init\` setup so PRODUCT.md and DESIGN.md are detected, created, or reconciled before downstream design research. Do not ask for or wait on a separate init stage.`,
|
|
66
|
+
],
|
|
67
|
+
[
|
|
68
|
+
"interview",
|
|
69
|
+
[
|
|
70
|
+
"Use your `ask_user_question` tool for important gaps you cannot infer from the request or repo.",
|
|
71
|
+
`Cover: (a) what to build and core jobs/screens; (b) output type — one of ${outputTypes}; (c) references to emulate (URLs, local paths, screenshots, or design docs).`,
|
|
72
|
+
"Ask 2-3 questions per round; propose inferred answers as options, not finished facts.",
|
|
73
|
+
"User-provided references are the PRIMARY visual authority and take precedence over DESIGN.md/PRODUCT.md where they conflict.",
|
|
74
|
+
].join("\n"),
|
|
75
|
+
],
|
|
76
|
+
[
|
|
77
|
+
"init_instructions",
|
|
78
|
+
[
|
|
79
|
+
"After the brief is confirmed, run `/skill:impeccable init` in this same stage.",
|
|
80
|
+
"Let impeccable init perform its own PRODUCT.md/DESIGN.md detection; do not rely on precomputed detection from the workflow runner.",
|
|
81
|
+
"Create missing PRODUCT.md and/or DESIGN.md when needed, and reconcile existing files against the confirmed brief. Never silently overwrite existing files.",
|
|
82
|
+
"When the files already exist, keep it light: load them, reconcile against the brief, and only ask about genuine gaps.",
|
|
83
|
+
"If headless, infer the most defensible brief/register from the prompt and repo signals, write explicit `## Gaps / Assumptions`, and never block.",
|
|
84
|
+
].join("\n"),
|
|
85
|
+
],
|
|
86
|
+
[
|
|
87
|
+
"output_format",
|
|
88
|
+
`Return the structured final answer with: \`brief\` (confirmed expanded design brief), \`output_type\` (one of ${outputTypes}), and \`references\` (array of verbatim URLs/paths; empty array when none). In your visible summary, also include PRODUCT.md/DESIGN.md files written or reconciled and any assumptions.`,
|
|
89
|
+
],
|
|
90
|
+
])}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export async function runDiscoveryAndInit(args: {
|
|
94
|
+
readonly designContext: SetupDesignContext;
|
|
95
|
+
readonly prompt: string;
|
|
96
|
+
readonly discoveryConfig: SetupModelConfig;
|
|
97
|
+
}): Promise<{
|
|
98
|
+
readonly discovery: DiscoveryDecision;
|
|
99
|
+
readonly discoveryContext: string;
|
|
100
|
+
readonly projectContext: ProjectDesignContextResult;
|
|
101
|
+
}> {
|
|
102
|
+
const result = await args.designContext.task("discovery", {
|
|
103
|
+
prompt: buildDiscoveryInitPrompt(args.prompt),
|
|
104
|
+
...args.discoveryConfig,
|
|
105
|
+
});
|
|
106
|
+
const discovery = discoveryDecisionFromResult(result, args.prompt);
|
|
107
|
+
return {
|
|
108
|
+
discovery,
|
|
109
|
+
discoveryContext: renderDiscoveryContext(discovery),
|
|
110
|
+
projectContext: {
|
|
111
|
+
summary: [
|
|
112
|
+
"Ran `/skill:impeccable shape` + `/skill:impeccable init` in the combined discovery stage.",
|
|
113
|
+
(result.text ?? "").trim(),
|
|
114
|
+
].filter((part) => part.length > 0).join("\n\n"),
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
// 1. Reference discovery
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
/** Curated galleries of beautiful, current reference designs. */
|
|
124
|
+
export const REFERENCE_DESIGN_SITES: readonly { readonly name: string; readonly url: string }[] = [
|
|
125
|
+
{ name: "Awwwards", url: "https://www.awwwards.com/websites/" },
|
|
126
|
+
{ name: "recent.design", url: "https://recent.design/" },
|
|
127
|
+
{ name: "Dribbble (recent shots)", url: "https://dribbble.com/shots/recent" },
|
|
128
|
+
{ name: "Monet", url: "https://www.monet.design/c" },
|
|
129
|
+
{ name: "Motionsites", url: "https://motionsites.ai/" },
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
export const NO_REFERENCES_BRIEF =
|
|
133
|
+
"Reference discovery was skipped. Generate from the project design system and the prompt; do not fabricate external references.";
|
|
134
|
+
|
|
135
|
+
export function buildReferenceDiscoveryPrompt(args: {
|
|
136
|
+
readonly prompt: string;
|
|
137
|
+
readonly outputType: string;
|
|
138
|
+
readonly designContextHint: string;
|
|
139
|
+
readonly artifactDir: string;
|
|
140
|
+
readonly browserBootstrapRules: string;
|
|
141
|
+
}): string {
|
|
142
|
+
const siteList = REFERENCE_DESIGN_SITES.map(
|
|
143
|
+
(site, index) => `${index + 1}. ${site.name} — ${site.url}`,
|
|
144
|
+
).join("\n");
|
|
145
|
+
return taggedPrompt([
|
|
146
|
+
[
|
|
147
|
+
"role",
|
|
148
|
+
"You are an opinionated staff design engineer and design researcher curating best-in-class, current visual references.",
|
|
149
|
+
],
|
|
150
|
+
[
|
|
151
|
+
"objective",
|
|
152
|
+
`Find beautiful, current reference designs the team can heavily reference to build a stunning ${args.outputType} for: ${args.prompt}. Open each gallery, CLICK THROUGH to the actual design pages of interest, and — ideally — record a scroll-through video of each page so its ANIMATIONS are captured (with a full-page screenshot as a supplement/fallback) plus its real destination URL. Apply the impeccable \`extract\` sub-skill to lift concrete, citable design traits — never vague adjectives.`,
|
|
153
|
+
],
|
|
154
|
+
["reference_galleries", siteList],
|
|
155
|
+
["design_context", args.designContextHint],
|
|
156
|
+
["browser_use_guidelines", args.browserBootstrapRules],
|
|
157
|
+
["screenshot_dir", args.artifactDir],
|
|
158
|
+
[
|
|
159
|
+
"instructions",
|
|
160
|
+
[
|
|
161
|
+
"1. Use the playwright-cli skill to open each gallery above; if `playwright-cli` reports a missing browser executable, follow the bootstrap rules and retry once.",
|
|
162
|
+
"2. On each gallery, scan the thumbnail grid and pick 1-3 designs of interest whose aesthetic fits this brief.",
|
|
163
|
+
"3. CLICK INTO each chosen design to open its ACTUAL page — the live site or project detail the thumbnail links to (for example the gallery's 'visit site' / shot-detail link). Do NOT capture the gallery grid or the thumbnail; navigate to the real design page first.",
|
|
164
|
+
`4. Capture the design's MOTION, not just a still: record a scroll-through video of the ENTIRE page so scroll-triggered animations, parallax, reveals, and transitions are captured. Start with \`playwright-cli video-start ${join(args.artifactDir, "ref-<site>-<n>.webm")}\`, then scroll smoothly from top to bottom — a \`playwright-cli run-code\` script that scrolls in small increments with short waits, or repeated \`playwright-cli mousewheel 0 600\` with pauses — so animations fire and lazy content loads, then \`playwright-cli video-stop\`.`,
|
|
165
|
+
`5. ALSO take a FULL-PAGE still as a supplement/fallback: \`playwright-cli screenshot --full-page --filename=${join(args.artifactDir, "ref-<site>-<n>.png")}\`. If video recording is unavailable, the full-page screenshot is the minimum.`,
|
|
166
|
+
"6. Record the FULL destination URL you actually landed on (the live site / project URL, not the gallery listing URL), plus the work's title and author.",
|
|
167
|
+
"7. For every reference, extract the CONCRETE transferable trait (layout topology, type pairing, color strategy, spacing rhythm) AND the MOTION vocabulary you saw in the recording (entrance animations, scroll reveals, easing, parallax, hover/active states) — cite what you observed on the real page, not what you imagine.",
|
|
168
|
+
"8. For on-brand fit, consult the project's DESIGN.md / PRODUCT.md and the ds-* discovery evidence in <design_context>; prefer references that fit, and flag any that would require departing from the project's system.",
|
|
169
|
+
"9. After curating the strongest options, use ask_user_question to ask the user which reference direction they prefer. Offer 2-4 concise choices drawn from the best references/directions and include a clear `None of these fit` choice when appropriate.",
|
|
170
|
+
"10. If the user says none of the discovered references align with their preference, ask them to provide a reference image, screenshot, URL, or local file path for best results, and include that request and any answer in the final brief.",
|
|
171
|
+
"11. If `playwright-cli` is unavailable or a site blocks automation, fall back to web search / page fetch to reach the actual design pages, and clearly mark any reference you could not capture with a recording or full-page screenshot.",
|
|
172
|
+
"12. Never fabricate references or visual claims; if a gallery yielded nothing usable, say so.",
|
|
173
|
+
].join("\n"),
|
|
174
|
+
],
|
|
175
|
+
[
|
|
176
|
+
"output_format",
|
|
177
|
+
[
|
|
178
|
+
"Markdown sections:",
|
|
179
|
+
"1. Curated references (table: Source gallery | Work (title/author) | Full page URL (destination) | Scroll-through video path | Full-page screenshot path | Transferable trait (incl. motion) | On-brand?)",
|
|
180
|
+
"2. User preference check: which curated direction/reference the user preferred, or that none aligned and a reference image/screenshot/URL/path was requested for best results.",
|
|
181
|
+
"3. Synthesis: the 3-5 strongest directions to emulate for THIS design, ranked by fit, calling out motion/animation worth reproducing.",
|
|
182
|
+
"4. What to avoid (anti-references observed on the real pages).",
|
|
183
|
+
"5. Verification notes (which references have a scroll-through recording and/or full-page screenshot of the actual design page vs search-only).",
|
|
184
|
+
].join("\n"),
|
|
185
|
+
],
|
|
186
|
+
]);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Persist the curated references brief to `<artifactDir>/references.md`. Best-effort. */
|
|
190
|
+
export function persistReferencesBrief(artifactDir: string, brief: string): void {
|
|
191
|
+
try {
|
|
192
|
+
mkdirSync(artifactDir, { recursive: true });
|
|
193
|
+
writeFileSync(join(artifactDir, "references.md"), `${brief}\n`);
|
|
194
|
+
} catch {
|
|
195
|
+
/* best-effort durability; never block the workflow */
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
200
|
+
// 2. Live interactive QA prompt (user-feedback display/review stages)
|
|
201
|
+
// ---------------------------------------------------------------------------
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Build the interactive-QA prompt for the `user-feedback-*` stages. Drives
|
|
205
|
+
* `/skill:impeccable live` against the static preview so the user can pick
|
|
206
|
+
* elements in the browser, annotate, and accept on-brand variants; degrades to
|
|
207
|
+
* `playwright-cli show --annotate` and finally to a manual file path. The output
|
|
208
|
+
* labels (`user_notes`, `annotated_snapshot`, `live_changes`) are parsed by the
|
|
209
|
+
* generate/user-feedback loop.
|
|
210
|
+
*/
|
|
211
|
+
export function buildLivePreviewDisplayPrompt(args: {
|
|
212
|
+
readonly previewPath: string;
|
|
213
|
+
readonly previewFileUrl: string;
|
|
214
|
+
readonly browserBootstrapRules: string;
|
|
215
|
+
readonly iteration?: number;
|
|
216
|
+
readonly maxRefinements?: number;
|
|
217
|
+
readonly final?: boolean;
|
|
218
|
+
}): string {
|
|
219
|
+
const isInitial = args.iteration === undefined;
|
|
220
|
+
const isFinal = args.final === true;
|
|
221
|
+
const label = isInitial
|
|
222
|
+
? "the just-generated HTML artifact"
|
|
223
|
+
: `the revised preview after iteration ${args.iteration}/${args.maxRefinements}`;
|
|
224
|
+
const objective = isFinal
|
|
225
|
+
? `Show the user ${label} as the FINAL refinement pass and let them review it in the browser. This is the last automated iteration, so do NOT solicit change requests this run cannot apply — if the user wants further changes, tell them to re-run \`/workflow open-claude-design\`. Drive \`/skill:impeccable live\` for viewing/QA when possible; degrade gracefully.`
|
|
226
|
+
: `Make ${label} visible to the user, run an interactive design-QA session against it, then capture the user's feedback for the refinement loop. Drive \`/skill:impeccable live\` against the static preview when possible; degrade gracefully when browser automation is unavailable.`;
|
|
227
|
+
const interactiveQa = isFinal
|
|
228
|
+
? [
|
|
229
|
+
`1. Open the preview for a final review: run \`/skill:impeccable live\` (or \`playwright-cli open ${args.previewFileUrl}\`) so the user can inspect ${label} in the browser.`,
|
|
230
|
+
"2. Make clear this is the final automated refinement pass. Do NOT promise to apply further annotations; instead, tell the user exactly how to re-run the workflow to iterate again.",
|
|
231
|
+
].join("\n")
|
|
232
|
+
: [
|
|
233
|
+
`1. Run \`/skill:impeccable live\` targeted at the preview file so the user can pick elements in the browser, annotate them, and compare on-brand variants. The preview is a single static HTML file at ${args.previewPath}; point live at it (configure \`.impeccable/live/config.json\` for that file or pass \`--target ${args.previewPath}\` per the live reference) and open ${args.previewFileUrl} in the browser.`,
|
|
234
|
+
"2. For each element the user picks, follow the live contract: read any annotation screenshot, extract the page identity FIRST, then generate three DISTINCT on-brand variants and let the user accept one. Accepted variants are written into the preview HTML in place; do NOT branch the artifact.",
|
|
235
|
+
"3. Also handle the live `steer` path for page-level direction the user types/speaks, and treat any freeform prompt as the ceiling on direction.",
|
|
236
|
+
"4. Keep iterating until the user signals they are done with this round.",
|
|
237
|
+
].join("\n");
|
|
238
|
+
const outputFormat = isFinal
|
|
239
|
+
? [
|
|
240
|
+
"Markdown with: `display_method` (live | playwright-annotate | manual), `preview_path`, and `next_action_hint` (how to re-run the workflow for further changes).",
|
|
241
|
+
"Do NOT collect `user_notes` or `live_changes`: this final pass cannot apply them, so don't invite feedback that would go nowhere.",
|
|
242
|
+
].join("\n")
|
|
243
|
+
: [
|
|
244
|
+
"Markdown with these exact labels so the refinement loop can parse the captured feedback:",
|
|
245
|
+
"`display_method` (live | playwright-annotate | manual)",
|
|
246
|
+
"`preview_path`",
|
|
247
|
+
"`live_changes` (summary of every element/variant the user ACCEPTED in the live session; `none` when no live edits were made)",
|
|
248
|
+
"`annotated_snapshot` (path to any annotated screenshot, if captured)",
|
|
249
|
+
"`user_notes` (the user's verbatim notes/annotations for the next iteration; `none` when the user gave no notes)",
|
|
250
|
+
"`next_action_hint`",
|
|
251
|
+
].join("\n");
|
|
252
|
+
return taggedPrompt([
|
|
253
|
+
[
|
|
254
|
+
"role",
|
|
255
|
+
"You are an opinionated staff design engineer running interactive `live` QA so the user can iterate on the design in a real browser.",
|
|
256
|
+
],
|
|
257
|
+
["objective", objective],
|
|
258
|
+
["preview_path", args.previewPath],
|
|
259
|
+
["preview_file_url", args.previewFileUrl],
|
|
260
|
+
["browser_use_guidelines", args.browserBootstrapRules],
|
|
261
|
+
["interactive_live_qa", interactiveQa],
|
|
262
|
+
[
|
|
263
|
+
"graceful_degradation",
|
|
264
|
+
[
|
|
265
|
+
`If \`/skill:impeccable live\` cannot boot (no dev server/HMR for the static file, missing config, or sandbox limits), fall back to opening the preview directly: \`playwright-cli open ${args.previewFileUrl}\`, then \`playwright-cli snapshot\`${isFinal ? "" : " and `playwright-cli show --annotate` so the user can draw/type notes on the page"}. If a \`playwright-cli\` command reports a missing browser executable, follow the bootstrap rules and retry once.`,
|
|
266
|
+
`If \`playwright-cli\` is also unavailable, print a clear instruction block telling the user to open the file manually at ${args.previewPath} (or ${args.previewFileUrl}).`,
|
|
267
|
+
"Never block the workflow on unavailable tooling; always exit with a non-empty status string.",
|
|
268
|
+
].join("\n"),
|
|
269
|
+
],
|
|
270
|
+
["output_format", outputFormat],
|
|
271
|
+
]);
|
|
272
|
+
}
|
|
@@ -18,15 +18,6 @@ export type OutputType = (typeof OUTPUT_TYPES)[number];
|
|
|
18
18
|
export const DEFAULT_OUTPUT_TYPE: OutputType = "prototype";
|
|
19
19
|
export const DEFAULT_MAX_REFINEMENTS = 3;
|
|
20
20
|
|
|
21
|
-
/**
|
|
22
|
-
* Read-only builtin tools granted to the structured-decision stages
|
|
23
|
-
* (user-feedback refinement gate and pre-export gate) so they can actually
|
|
24
|
-
* inspect the on-disk `preview.html` before emitting their decision. The
|
|
25
|
-
* artifact stays immutable here — writes/edits belong to apply-changes and
|
|
26
|
-
* forced-fix, so this list deliberately excludes write/edit/bash.
|
|
27
|
-
*/
|
|
28
|
-
export const READ_ONLY_TOOLS = ["read", "grep", "ls"] as const;
|
|
29
|
-
|
|
30
21
|
type PromptSection = readonly [tag: string, content: string];
|
|
31
22
|
|
|
32
23
|
export function taggedPrompt(sections: readonly PromptSection[]): string {
|
|
@@ -60,69 +51,59 @@ export function isFileLike(value: string): boolean {
|
|
|
60
51
|
return trimmed.length > 0 && !isUrl(trimmed);
|
|
61
52
|
}
|
|
62
53
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
export
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
};
|
|
54
|
+
/**
|
|
55
|
+
* Whether the browser-centric workflow should exit early instead of generating
|
|
56
|
+
* artifacts no one can review interactively. True only when the playwright-cli
|
|
57
|
+
* browser is unavailable AND we are not under the test harness (`NODE_ENV=test`,
|
|
58
|
+
* which always skips the global install and runs headlessly to completion).
|
|
59
|
+
*/
|
|
60
|
+
export function shouldEarlyExitForBrowser(
|
|
61
|
+
browserAvailable: boolean,
|
|
62
|
+
nodeEnv: string | undefined,
|
|
63
|
+
): boolean {
|
|
64
|
+
return !browserAvailable && nodeEnv !== "test";
|
|
65
|
+
}
|
|
76
66
|
|
|
77
|
-
export type
|
|
78
|
-
readonly
|
|
79
|
-
readonly
|
|
80
|
-
readonly
|
|
67
|
+
export type DiscoveryDecision = {
|
|
68
|
+
readonly brief: string;
|
|
69
|
+
readonly output_type: OutputType;
|
|
70
|
+
readonly references: readonly string[];
|
|
81
71
|
};
|
|
82
72
|
|
|
83
|
-
export const
|
|
84
|
-
{
|
|
85
|
-
ready_for_export: Type.Boolean(),
|
|
86
|
-
rationale: Type.String(),
|
|
87
|
-
required_changes: Type.Array(Type.String()),
|
|
88
|
-
},
|
|
89
|
-
{ additionalProperties: false },
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
const exportGateFindingSchema = Type.Object(
|
|
73
|
+
export const discoveryDecisionSchema = Type.Object(
|
|
93
74
|
{
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
must_fix_action: Type.String(),
|
|
98
|
-
severity: Type.Literal("P0"),
|
|
75
|
+
brief: Type.String(),
|
|
76
|
+
output_type: Type.Union([...OUTPUT_TYPES].map((value) => Type.Literal(value))),
|
|
77
|
+
references: Type.Array(Type.String()),
|
|
99
78
|
},
|
|
100
79
|
{ additionalProperties: false },
|
|
101
80
|
);
|
|
102
81
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
82
|
+
/**
|
|
83
|
+
* Parse the discovery stage's structured result, tolerating a missing/invalid
|
|
84
|
+
* structured payload (headless / mock runs) by falling back to the raw prompt as
|
|
85
|
+
* the brief, the default output type, and an empty reference list.
|
|
86
|
+
*/
|
|
87
|
+
export function discoveryDecisionFromResult(
|
|
88
|
+
result: WorkflowTaskResult,
|
|
89
|
+
fallbackBrief: string,
|
|
90
|
+
): DiscoveryDecision {
|
|
91
|
+
const decision = result.structured as Partial<DiscoveryDecision> | undefined;
|
|
92
|
+
const brief =
|
|
93
|
+
typeof decision?.brief === "string" && decision.brief.trim().length > 0
|
|
94
|
+
? decision.brief.trim()
|
|
95
|
+
: fallbackBrief;
|
|
96
|
+
const references = Array.isArray(decision?.references)
|
|
97
|
+
? decision.references
|
|
98
|
+
.filter((ref): ref is string => typeof ref === "string")
|
|
99
|
+
.map((ref) => ref.trim())
|
|
100
|
+
.filter((ref) => ref.length > 0)
|
|
101
|
+
: [];
|
|
102
|
+
return {
|
|
103
|
+
brief,
|
|
104
|
+
output_type: normalizeOutputType(decision?.output_type),
|
|
105
|
+
references,
|
|
106
|
+
};
|
|
126
107
|
}
|
|
127
108
|
|
|
128
109
|
export function joinResults(results: readonly WorkflowTaskResult[]): string {
|
|
@@ -144,10 +125,15 @@ export function prepareArtifactDir(cwd = process.cwd()): {
|
|
|
144
125
|
readonly specPath: string;
|
|
145
126
|
} {
|
|
146
127
|
const runId = `${new Date().toISOString().replace(/[:.]/g, "-")}-${Math.random().toString(36).slice(2, 8)}`;
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
128
|
+
// Under automated tests, prefer the OS tmpdir so a full `d.run()` does not
|
|
129
|
+
// pollute the project's `specs/design/` tree with per-run artifact folders.
|
|
130
|
+
const candidates =
|
|
131
|
+
process.env.NODE_ENV === "test"
|
|
132
|
+
? [join(tmpdir(), "open-claude-design", runId)]
|
|
133
|
+
: [
|
|
134
|
+
join(cwd, "specs", "design", runId),
|
|
135
|
+
join(tmpdir(), "open-claude-design", runId),
|
|
136
|
+
];
|
|
151
137
|
for (const candidate of candidates) {
|
|
152
138
|
try {
|
|
153
139
|
mkdirSync(candidate, { recursive: true });
|
|
@@ -187,6 +173,10 @@ export const ANTI_SLOP_RULES = [
|
|
|
187
173
|
"Commit to a specific aesthetic direction; do not hedge with generic SaaS defaults.",
|
|
188
174
|
].join("\n");
|
|
189
175
|
|
|
176
|
+
/** Reference-import precedence note shared by import, generation, and refinement. */
|
|
177
|
+
export const REFERENCE_PRECEDENCE =
|
|
178
|
+
"User-provided references in <reference_context> are the PRIMARY visual authority: when they conflict with DESIGN.md/PRODUCT.md, follow the references. DESIGN.md governs decisions the references do not cover; PRODUCT.md still governs strategic register/voice.";
|
|
179
|
+
|
|
190
180
|
export type PlaywrightCliStatus = {
|
|
191
181
|
/** Whether the `playwright-cli` command is expected to be available to downstream stages. */
|
|
192
182
|
readonly available: boolean;
|
|
@@ -1,20 +1,16 @@
|
|
|
1
|
-
import type { WorkflowDefinition,
|
|
1
|
+
import type { WorkflowDefinition, WorkflowOutputValues } from "../src/authoring.js";
|
|
2
2
|
|
|
3
3
|
export type OpenClaudeDesignOutputType = "prototype" | "wireframe" | "page" | "component" | "theme" | "tokens";
|
|
4
4
|
|
|
5
|
-
export type OpenClaudeDesignWorkflowInputs =
|
|
5
|
+
export type OpenClaudeDesignWorkflowInputs = {
|
|
6
6
|
readonly prompt: string;
|
|
7
|
-
readonly
|
|
8
|
-
readonly output_type: OpenClaudeDesignOutputType;
|
|
9
|
-
readonly design_system?: string;
|
|
7
|
+
readonly discover_references: boolean;
|
|
10
8
|
readonly max_refinements: number;
|
|
11
9
|
};
|
|
12
10
|
|
|
13
|
-
export type OpenClaudeDesignWorkflowRunInputs =
|
|
11
|
+
export type OpenClaudeDesignWorkflowRunInputs = {
|
|
14
12
|
readonly prompt: string;
|
|
15
|
-
readonly
|
|
16
|
-
readonly output_type?: OpenClaudeDesignOutputType;
|
|
17
|
-
readonly design_system?: string;
|
|
13
|
+
readonly discover_references?: boolean;
|
|
18
14
|
readonly max_refinements?: number;
|
|
19
15
|
};
|
|
20
16
|
|
|
@@ -2,22 +2,18 @@
|
|
|
2
2
|
* Builtin workflow: open-claude-design
|
|
3
3
|
*
|
|
4
4
|
* Adapts Atomic SDK's Claude Design workflow to the local workflow SDK:
|
|
5
|
-
* design-system
|
|
6
|
-
*
|
|
5
|
+
* combined discovery/init, design-system/reference research, generation, bounded
|
|
6
|
+
* refinement, export, and final display run through ctx.task()/ctx.parallel().
|
|
7
7
|
*
|
|
8
8
|
* Every stage prompt invokes the specific impeccable sub-skill that maps to
|
|
9
9
|
* its role (see https://github.com/pbakaus/impeccable/tree/main/site/content/skills):
|
|
10
10
|
*
|
|
11
11
|
* onboarding → impeccable `document` / `extract` / `audit`
|
|
12
12
|
* import → impeccable `extract`
|
|
13
|
-
*
|
|
14
|
-
* user-feedback → impeccable `
|
|
15
|
-
* critique-N → impeccable `critique`
|
|
16
|
-
* screenshot-N → impeccable `audit` + `live`
|
|
17
|
-
* apply-changes → impeccable `polish`
|
|
18
|
-
* pre-export → impeccable `audit`
|
|
19
|
-
* forced-fix → impeccable `harden`
|
|
13
|
+
* generate-N → impeccable `craft` / `polish` (HTML preview)
|
|
14
|
+
* user-feedback → impeccable `live` (browser review + user notes)
|
|
20
15
|
* exporter → impeccable `document` (rich HTML spec)
|
|
16
|
+
* final-display → opens/surfaces the exported HTML spec
|
|
21
17
|
*/
|
|
22
18
|
|
|
23
19
|
import { Type } from "typebox";
|
|
@@ -25,39 +21,31 @@ import { workflow } from "../src/authoring/workflow.js";
|
|
|
25
21
|
import { runOpenClaudeDesignWorkflow } from "./open-claude-design-runner.js";
|
|
26
22
|
import {
|
|
27
23
|
DEFAULT_MAX_REFINEMENTS,
|
|
28
|
-
DEFAULT_OUTPUT_TYPE,
|
|
29
|
-
OUTPUT_TYPES,
|
|
30
24
|
} from "./open-claude-design-utils.js";
|
|
31
25
|
|
|
32
26
|
export default workflow({
|
|
33
27
|
name: "open-claude-design",
|
|
34
|
-
description: "AI-powered design workflow: design-system
|
|
28
|
+
description: "AI-powered design workflow: combined discovery/init → design-system/reference research → curated reference discovery → HTML generation → live-driven refinement → rich HTML handoff. The discovery stage asks what to build, the output type, and which references to emulate, then runs impeccable init for PRODUCT.md/DESIGN.md (references take precedence over project context). The user iteratively reviews the generated HTML.",
|
|
35
29
|
inputs: {
|
|
36
30
|
prompt: Type.String({
|
|
37
|
-
description: "What to design (for example, a dashboard, page, component, or prototype).",
|
|
31
|
+
description: "What to design (for example, a dashboard, page, component, or prototype). The discovery stage refines this into a confirmed brief and asks for the output type and references.",
|
|
38
32
|
}),
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
})),
|
|
42
|
-
output_type: Type.Union(
|
|
43
|
-
[...OUTPUT_TYPES].map((value) => Type.Literal(value)),
|
|
44
|
-
{ default: DEFAULT_OUTPUT_TYPE, description: "Kind of design artifact to produce." },
|
|
45
|
-
),
|
|
46
|
-
design_system: Type.Optional(Type.String({
|
|
33
|
+
discover_references: Type.Boolean({
|
|
34
|
+
default: true,
|
|
47
35
|
description:
|
|
48
|
-
"
|
|
49
|
-
})
|
|
36
|
+
"Discover beautiful, current reference designs from notable design websites (Awwwards, recent.design, Dribbble, Monet, Motionsites) and feed them to generation. Set false to skip the network/browser reference pass.",
|
|
37
|
+
}),
|
|
50
38
|
max_refinements: Type.Number({
|
|
51
39
|
default: DEFAULT_MAX_REFINEMENTS,
|
|
52
|
-
description: `Maximum
|
|
40
|
+
description: `Maximum generate/user-feedback loop iterations (default ${DEFAULT_MAX_REFINEMENTS}).`,
|
|
53
41
|
}),
|
|
54
42
|
},
|
|
55
43
|
outputs: {
|
|
56
44
|
output_type: Type.Optional(Type.String({ description: "Kind of design artifact produced." })),
|
|
57
|
-
design_system: Type.Optional(Type.String({ description: "Design system source used for generation:
|
|
45
|
+
design_system: Type.Optional(Type.String({ description: "Design system source used for generation: the project-derived design system." })),
|
|
58
46
|
artifact: Type.Optional(Type.String({ description: "Latest final design summary from the approved preview artifact." })),
|
|
59
47
|
handoff: Type.Optional(Type.String({ description: "Final rich HTML spec and implementation handoff summary." })),
|
|
60
|
-
approved_for_export: Type.Optional(Type.Boolean({ description: "Whether refinement completed before
|
|
48
|
+
approved_for_export: Type.Optional(Type.Boolean({ description: "Whether refinement completed before export." })),
|
|
61
49
|
refinements_completed: Type.Optional(Type.Number({ description: "Number of refinement iterations completed." })),
|
|
62
50
|
import_context: Type.Optional(Type.String({ description: "Reference-import context used during generation." })),
|
|
63
51
|
run_id: Type.Optional(Type.String({ description: "Per-run design workflow artifact identifier." })),
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared prompt-refinement stage used by the ralph and goal workflows.
|
|
3
|
+
*
|
|
4
|
+
* Before the main work loop begins, both workflows run this single
|
|
5
|
+
* `prompt-refinement` stage. It invokes the prompt-engineer skill
|
|
6
|
+
* (`/skill:prompt-engineer`) to sharpen the raw user request into a clearer,
|
|
7
|
+
* more actionable objective using the Workflow Best Practices prompt anatomy
|
|
8
|
+
* documented in `packages/coding-agent/docs/workflows.md`. The refined request
|
|
9
|
+
* replaces the original as the operative objective downstream; the original is
|
|
10
|
+
* preserved by each workflow for reporting.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { WorkflowModelValue, WorkflowTaskOptions, WorkflowTaskResult } from "../src/shared/types.js";
|
|
14
|
+
|
|
15
|
+
export type PromptSection = readonly [tag: string, content: string];
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Clarity rubric mirrored from the "## Workflow Best Practices" section of
|
|
19
|
+
* `docs/workflows.md` (the user-facing docs under packages/coding-agent/docs).
|
|
20
|
+
* The refinement stage makes each element explicit where it can be reasonably
|
|
21
|
+
* inferred from the raw request.
|
|
22
|
+
*/
|
|
23
|
+
export const PROMPT_REFINEMENT_CRITERIA = [
|
|
24
|
+
"Apply the workflow best practices documented in the `## Workflow Best Practices` section of `docs/workflows.md`. Treat that section as the authoritative prompt-anatomy rubric: use its Objective, Context, Scope, Non-goals, Done criteria, Validation command, Reporting requirements, and Stop conditions when refining the request.",
|
|
25
|
+
"Objective — state what should be true when the work is complete.",
|
|
26
|
+
"Context — note why it matters and where the relevant code or area likely lives.",
|
|
27
|
+
"Scope — state what is allowed to change (the smallest correct change).",
|
|
28
|
+
"Non-goals — state what to avoid (unrelated refactors, redesigns, or behavior changes outside this case).",
|
|
29
|
+
"Done criteria — list verifiable completion signals: new behavior works, existing behavior is unchanged, and the validation command passes.",
|
|
30
|
+
"Validation command — name the targeted check that proves the result.",
|
|
31
|
+
"Reporting requirements — changed files, validation results, and remaining risks must be reported.",
|
|
32
|
+
"Stop conditions — name the cases where the agent should stop and ask first (public API, security, data migration, etc.).",
|
|
33
|
+
].join("\n");
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Build the prompt sent to the prompt-refinement stage. The refined request is
|
|
37
|
+
* returned verbatim (no fences or preamble) so it can replace the original
|
|
38
|
+
* request as the operative objective for the rest of the workflow.
|
|
39
|
+
*/
|
|
40
|
+
export function renderPromptRefinementPrompt(args: {
|
|
41
|
+
readonly request: string;
|
|
42
|
+
readonly workflowLabel: string;
|
|
43
|
+
readonly workflowCwdContext?: PromptSection;
|
|
44
|
+
}): string {
|
|
45
|
+
const sections: readonly string[] = [
|
|
46
|
+
`/skill:prompt-engineer Refine the following user request into a clearer, more actionable objective for the ${args.workflowLabel} workflow. Improve clarity and completeness using the rubric below without changing the user's intent, expanding scope, or inventing requirements that cannot be reasonably inferred from the request.`,
|
|
47
|
+
`<original_request>\n${args.request}\n</original_request>`,
|
|
48
|
+
`<clarity_rubric>\nApply the Workflow Best Practices prompt anatomy. Make each of the following explicit where it can be reasonably inferred from the original request:\n${PROMPT_REFINEMENT_CRITERIA}\n</clarity_rubric>`,
|
|
49
|
+
[
|
|
50
|
+
"<refinement_rules>",
|
|
51
|
+
"- Preserve the user's original intent and scope; do not add unrelated work.",
|
|
52
|
+
"- If the original request is already clear and complete, return it essentially unchanged with only clarity improvements.",
|
|
53
|
+
"- Where a criterion cannot be reasonably inferred, state it as a concise assumption or a 'to confirm' note rather than fabricating specifics.",
|
|
54
|
+
"- Do not implement anything, run commands, or edit files. This stage only produces the refined request text.",
|
|
55
|
+
"</refinement_rules>",
|
|
56
|
+
].join("\n"),
|
|
57
|
+
`<output_format>\nReturn ONLY the refined request. No preamble, no explanation, and no Markdown fences. The returned text replaces the original request as the operative objective for the rest of the workflow, so it must be a single self-contained request.\n</output_format>`,
|
|
58
|
+
];
|
|
59
|
+
const tail = args.workflowCwdContext === undefined
|
|
60
|
+
? []
|
|
61
|
+
: [`<${args.workflowCwdContext[0]}>\n${args.workflowCwdContext[1].trim()}\n</${args.workflowCwdContext[0]}>`];
|
|
62
|
+
return [...sections, ...tail].join("\n\n");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Minimal context surface required to run a tracked refinement stage. */
|
|
66
|
+
type PromptRefinementContext = {
|
|
67
|
+
task(name: string, options: WorkflowTaskOptions): Promise<WorkflowTaskResult>;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/** Model-chain + tool gating forwarded to the refinement stage session. */
|
|
71
|
+
export type PromptRefinementModelConfig = {
|
|
72
|
+
readonly model?: WorkflowModelValue;
|
|
73
|
+
readonly fallbackModels?: readonly string[];
|
|
74
|
+
readonly noTools?: "all" | "builtin";
|
|
75
|
+
readonly excludedTools?: readonly string[];
|
|
76
|
+
readonly tools?: readonly string[];
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Run the shared `prompt-refinement` stage once and return the refined request.
|
|
81
|
+
* Falls back to the original request when the stage produces no usable text.
|
|
82
|
+
*/
|
|
83
|
+
export async function runPromptRefinementStage(
|
|
84
|
+
ctx: PromptRefinementContext,
|
|
85
|
+
options: {
|
|
86
|
+
readonly request: string;
|
|
87
|
+
readonly workflowLabel: string;
|
|
88
|
+
readonly workflowCwdContext?: PromptSection;
|
|
89
|
+
readonly modelConfig: PromptRefinementModelConfig;
|
|
90
|
+
},
|
|
91
|
+
): Promise<string> {
|
|
92
|
+
const result = await ctx.task("prompt-refinement", {
|
|
93
|
+
prompt: renderPromptRefinementPrompt({
|
|
94
|
+
request: options.request,
|
|
95
|
+
workflowLabel: options.workflowLabel,
|
|
96
|
+
...(options.workflowCwdContext === undefined ? {} : { workflowCwdContext: options.workflowCwdContext }),
|
|
97
|
+
}),
|
|
98
|
+
...options.modelConfig,
|
|
99
|
+
});
|
|
100
|
+
const refined = (result.text ?? "").trim();
|
|
101
|
+
return refined.length > 0 ? refined : options.request;
|
|
102
|
+
}
|