mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-05 05:05:08 +02:00
fix: resolve merge conflicts — keep main's v0.15.4.0, merge CHANGELOG entries
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -132,6 +132,63 @@ function extractNameAndDescription(content: string): { name: string; description
|
||||
return { name, description };
|
||||
}
|
||||
|
||||
// ─── Voice Trigger Processing ────────────────────────────────
|
||||
|
||||
/**
|
||||
* Extract voice-triggers YAML list from frontmatter.
|
||||
* Returns an array of trigger strings, or [] if no voice-triggers field.
|
||||
*/
|
||||
function extractVoiceTriggers(content: string): string[] {
|
||||
const fmStart = content.indexOf('---\n');
|
||||
if (fmStart !== 0) return [];
|
||||
const fmEnd = content.indexOf('\n---', fmStart + 4);
|
||||
if (fmEnd === -1) return [];
|
||||
const frontmatter = content.slice(fmStart + 4, fmEnd);
|
||||
|
||||
const triggers: string[] = [];
|
||||
let inVoice = false;
|
||||
for (const line of frontmatter.split('\n')) {
|
||||
if (/^voice-triggers:/.test(line)) { inVoice = true; continue; }
|
||||
if (inVoice) {
|
||||
const m = line.match(/^\s+-\s+"(.+)"$/);
|
||||
if (m) triggers.push(m[1]);
|
||||
else if (!/^\s/.test(line)) break;
|
||||
}
|
||||
}
|
||||
return triggers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Preprocess voice triggers: fold voice-triggers YAML field into description,
|
||||
* then strip the field from frontmatter. Must run BEFORE transformFrontmatter
|
||||
* and extractNameAndDescription so all hosts see the updated description.
|
||||
*/
|
||||
function processVoiceTriggers(content: string): string {
|
||||
const triggers = extractVoiceTriggers(content);
|
||||
if (triggers.length === 0) return content;
|
||||
|
||||
// Strip voice-triggers block from frontmatter
|
||||
content = content.replace(/^voice-triggers:\n(?:\s+-\s+"[^"]*"\n?)*/m, '');
|
||||
|
||||
// Get current description (after stripping voice-triggers, so it's clean)
|
||||
const { description } = extractNameAndDescription(content);
|
||||
if (!description) return content;
|
||||
|
||||
// Build new description with voice triggers appended
|
||||
const voiceLine = `Voice triggers (speech-to-text aliases): ${triggers.map(t => `"${t}"`).join(', ')}.`;
|
||||
const newDescription = description + '\n' + voiceLine;
|
||||
|
||||
// Replace old indented description with new in frontmatter
|
||||
const oldIndented = description.split('\n').map(l => ` ${l}`).join('\n');
|
||||
const newIndented = newDescription.split('\n').map(l => ` ${l}`).join('\n');
|
||||
content = content.replace(oldIndented, newIndented);
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
// Export for testing
|
||||
export { extractVoiceTriggers, processVoiceTriggers };
|
||||
|
||||
const OPENAI_SHORT_DESCRIPTION_LIMIT = 120;
|
||||
|
||||
function condenseOpenAIShortDescription(description: string): string {
|
||||
@@ -163,8 +220,10 @@ policy:
|
||||
*/
|
||||
function transformFrontmatter(content: string, host: Host): string {
|
||||
if (host === 'claude') {
|
||||
// Strip sensitive: field from Claude output (only Factory uses it)
|
||||
return content.replace(/^sensitive:\s*true\n/m, '');
|
||||
// Strip fields not used by Claude: sensitive (Factory-only), voice-triggers (folded into description by preprocessing)
|
||||
content = content.replace(/^sensitive:\s*true\n/m, '');
|
||||
content = content.replace(/^voice-triggers:\n(?:\s+-\s+"[^"]*"\n?)*/m, '');
|
||||
return content;
|
||||
}
|
||||
|
||||
const fmStart = content.indexOf('---\n');
|
||||
@@ -364,13 +423,22 @@ function processTemplate(tmplPath: string, host: Host = 'claude'): { outputPath:
|
||||
throw new Error(`Unresolved placeholders in ${relTmplPath}: ${remaining.join(', ')}`);
|
||||
}
|
||||
|
||||
// Preprocess voice triggers: fold into description, strip field from frontmatter.
|
||||
// Must run BEFORE transformFrontmatter so all hosts see the updated description,
|
||||
// and BEFORE extractedDescription is used by external host metadata.
|
||||
content = processVoiceTriggers(content);
|
||||
|
||||
// Re-extract description AFTER voice trigger preprocessing so Codex openai.yaml
|
||||
// metadata gets the updated description with voice triggers included.
|
||||
const postProcessDescription = extractNameAndDescription(content).description;
|
||||
|
||||
// For Claude: strip sensitive: field (only Factory uses it)
|
||||
// For external hosts: route output, transform frontmatter, rewrite paths
|
||||
let symlinkLoop = false;
|
||||
if (host === 'claude') {
|
||||
content = transformFrontmatter(content, host);
|
||||
} else {
|
||||
const result = processExternalHost(content, tmplContent, host, skillDir, extractedDescription, ctx, extractedName || undefined);
|
||||
const result = processExternalHost(content, tmplContent, host, skillDir, postProcessDescription, ctx, extractedName || undefined);
|
||||
content = result.content;
|
||||
outputPath = result.outputPath;
|
||||
symlinkLoop = result.symlinkLoop;
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* DX Framework resolver
|
||||
*
|
||||
* Shared principles, characteristics, cognitive patterns, and scoring rubric
|
||||
* for /plan-devex-review and /devex-review. Compact (~150 lines).
|
||||
*
|
||||
* Hall of Fame examples are NOT included here. They live in
|
||||
* plan-devex-review/dx-hall-of-fame.md and are loaded on-demand per pass
|
||||
* to avoid prompt bloat.
|
||||
*/
|
||||
import type { TemplateContext } from './types';
|
||||
|
||||
export function generateDxFramework(ctx: TemplateContext): string {
|
||||
const hallOfFamePath = `${ctx.paths.skillRoot}/plan-devex-review/dx-hall-of-fame.md`;
|
||||
|
||||
return `## DX First Principles
|
||||
|
||||
These are the laws. Every recommendation traces back to one of these.
|
||||
|
||||
1. **Zero friction at T0.** First five minutes decide everything. One click to start. Hello world without reading docs. No credit card. No demo call.
|
||||
2. **Incremental steps.** Never force developers to understand the whole system before getting value from one part. Gentle ramp, not cliff.
|
||||
3. **Learn by doing.** Playgrounds, sandboxes, copy-paste code that works in context. Reference docs are necessary but never sufficient.
|
||||
4. **Decide for me, let me override.** Opinionated defaults are features. Escape hatches are requirements. Strong opinions, loosely held.
|
||||
5. **Fight uncertainty.** Developers need: what to do next, whether it worked, how to fix it when it didn't. Every error = problem + cause + fix.
|
||||
6. **Show code in context.** Hello world is a lie. Show real auth, real error handling, real deployment. Solve 100% of the problem.
|
||||
7. **Speed is a feature.** Iteration speed is everything. Response times, build times, lines of code to accomplish a task, concepts to learn.
|
||||
8. **Create magical moments.** What would feel like magic? Stripe's instant API response. Vercel's push-to-deploy. Find yours and make it the first thing developers experience.
|
||||
|
||||
## The Seven DX Characteristics
|
||||
|
||||
| # | Characteristic | What It Means | Gold Standard |
|
||||
|---|---------------|---------------|---------------|
|
||||
| 1 | **Usable** | Simple to install, set up, use. Intuitive APIs. Fast feedback. | Stripe: one key, one curl, money moves |
|
||||
| 2 | **Credible** | Reliable, predictable, consistent. Clear deprecation. Secure. | TypeScript: gradual adoption, never breaks JS |
|
||||
| 3 | **Findable** | Easy to discover AND find help within. Strong community. Good search. | React: every question answered on SO |
|
||||
| 4 | **Useful** | Solves real problems. Features match actual use cases. Scales. | Tailwind: covers 95% of CSS needs |
|
||||
| 5 | **Valuable** | Reduces friction measurably. Saves time. Worth the dependency. | Next.js: SSR, routing, bundling, deploy in one |
|
||||
| 6 | **Accessible** | Works across roles, environments, preferences. CLI + GUI. | VS Code: works for junior to principal |
|
||||
| 7 | **Desirable** | Best-in-class tech. Reasonable pricing. Community momentum. | Vercel: devs WANT to use it, not tolerate it |
|
||||
|
||||
## Cognitive Patterns — How Great DX Leaders Think
|
||||
|
||||
Internalize these; don't enumerate them.
|
||||
|
||||
1. **Chef-for-chefs** — Your users build products for a living. The bar is higher because they notice everything.
|
||||
2. **First five minutes obsession** — New dev arrives. Clock starts. Can they hello-world without docs, sales, or credit card?
|
||||
3. **Error message empathy** — Every error is pain. Does it identify the problem, explain the cause, show the fix, link to docs?
|
||||
4. **Escape hatch awareness** — Every default needs an override. No escape hatch = no trust = no adoption at scale.
|
||||
5. **Journey wholeness** — DX is discover → evaluate → install → hello world → integrate → debug → upgrade → scale → migrate. Every gap = a lost dev.
|
||||
6. **Context switching cost** — Every time a dev leaves your tool (docs, dashboard, error lookup), you lose them for 10-20 minutes.
|
||||
7. **Upgrade fear** — Will this break my production app? Clear changelogs, migration guides, codemods, deprecation warnings. Upgrades should be boring.
|
||||
8. **SDK completeness** — If devs write their own HTTP wrapper, you failed. If the SDK works in 4 of 5 languages, the fifth community hates you.
|
||||
9. **Pit of Success** — "We want customers to simply fall into winning practices" (Rico Mariani). Make the right thing easy, the wrong thing hard.
|
||||
10. **Progressive disclosure** — Simple case is production-ready, not a toy. Complex case uses the same API. SwiftUI: \\\`Button("Save") { save() }\\\` → full customization, same API.
|
||||
|
||||
## DX Scoring Rubric (0-10 calibration)
|
||||
|
||||
| Score | Meaning |
|
||||
|-------|---------|
|
||||
| 9-10 | Best-in-class. Stripe/Vercel tier. Developers rave about it. |
|
||||
| 7-8 | Good. Developers can use it without frustration. Minor gaps. |
|
||||
| 5-6 | Acceptable. Works but with friction. Developers tolerate it. |
|
||||
| 3-4 | Poor. Developers complain. Adoption suffers. |
|
||||
| 1-2 | Broken. Developers abandon after first attempt. |
|
||||
| 0 | Not addressed. No thought given to this dimension. |
|
||||
|
||||
**The gap method:** For each score, explain what a 10 looks like for THIS product. Then fix toward 10.
|
||||
|
||||
## TTHW Benchmarks (Time to Hello World)
|
||||
|
||||
| Tier | Time | Adoption Impact |
|
||||
|------|------|-----------------|
|
||||
| Champion | < 2 min | 3-4x higher adoption |
|
||||
| Competitive | 2-5 min | Baseline |
|
||||
| Needs Work | 5-10 min | Significant drop-off |
|
||||
| Red Flag | > 10 min | 50-70% abandon |
|
||||
|
||||
## Hall of Fame Reference
|
||||
|
||||
During each review pass, load the relevant section from:
|
||||
\\\`${hallOfFamePath}\\\`
|
||||
|
||||
Read ONLY the section for the current pass (e.g., "## Pass 1" for Getting Started).
|
||||
Do NOT read the entire file at once. This keeps context focused.`;
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import { generateLearningsSearch, generateLearningsLog } from './learnings';
|
||||
import { generateConfidenceCalibration } from './confidence';
|
||||
import { generateInvokeSkill } from './composition';
|
||||
import { generateReviewArmy } from './review-army';
|
||||
import { generateDxFramework } from './dx';
|
||||
|
||||
export const RESOLVERS: Record<string, ResolverFn> = {
|
||||
SLUG_EVAL: generateSlugEval,
|
||||
@@ -59,4 +60,5 @@ export const RESOLVERS: Record<string, ResolverFn> = {
|
||||
INVOKE_SKILL: generateInvokeSkill,
|
||||
CHANGELOG_WORKFLOW: generateChangelogWorkflow,
|
||||
REVIEW_ARMY: generateReviewArmy,
|
||||
DX_FRAMEWORK: generateDxFramework,
|
||||
};
|
||||
|
||||
@@ -508,6 +508,7 @@ Then write a \`## GSTACK REVIEW REPORT\` section to the end of the plan file:
|
||||
| Codex Review | \\\`/codex review\\\` | Independent 2nd opinion | 0 | — | — |
|
||||
| Eng Review | \\\`/plan-eng-review\\\` | Architecture & tests (required) | 0 | — | — |
|
||||
| Design Review | \\\`/plan-design-review\\\` | UI/UX gaps | 0 | — | — |
|
||||
| DX Review | \\\`/plan-devex-review\\\` | Developer experience gaps | 0 | — | — |
|
||||
|
||||
**VERDICT:** NO REVIEWS YET — run \\\`/autoplan\\\` for full review pipeline, or individual reviews above.
|
||||
\\\`\\\`\\\`
|
||||
|
||||
@@ -94,6 +94,10 @@ Parse each JSONL entry. Each skill logs different fields:
|
||||
→ Findings: "{issues_found} issues, {critical_gaps} critical gaps"
|
||||
- **plan-design-review**: \\\`status\\\`, \\\`initial_score\\\`, \\\`overall_score\\\`, \\\`unresolved\\\`, \\\`decisions_made\\\`, \\\`commit\\\`
|
||||
→ Findings: "score: {initial_score}/10 → {overall_score}/10, {decisions_made} decisions"
|
||||
- **plan-devex-review**: \\\`status\\\`, \\\`initial_score\\\`, \\\`overall_score\\\`, \\\`product_type\\\`, \\\`tthw_current\\\`, \\\`tthw_target\\\`, \\\`unresolved\\\`, \\\`commit\\\`
|
||||
→ Findings: "score: {initial_score}/10 → {overall_score}/10, TTHW: {tthw_current} → {tthw_target}"
|
||||
- **devex-review**: \\\`status\\\`, \\\`overall_score\\\`, \\\`product_type\\\`, \\\`tthw_measured\\\`, \\\`dimensions_tested\\\`, \\\`dimensions_inferred\\\`, \\\`boomerang\\\`, \\\`commit\\\`
|
||||
→ Findings: "score: {overall_score}/10, TTHW: {tthw_measured}, {dimensions_tested} tested/{dimensions_inferred} inferred"
|
||||
- **codex-review**: \\\`status\\\`, \\\`gate\\\`, \\\`findings\\\`, \\\`findings_fixed\\\`
|
||||
→ Findings: "{findings} findings, {findings_fixed}/{findings} fixed"
|
||||
|
||||
@@ -112,6 +116,7 @@ Produce this markdown table:
|
||||
| Codex Review | \\\`/codex review\\\` | Independent 2nd opinion | {runs} | {status} | {findings} |
|
||||
| Eng Review | \\\`/plan-eng-review\\\` | Architecture & tests (required) | {runs} | {status} | {findings} |
|
||||
| Design Review | \\\`/plan-design-review\\\` | UI/UX gaps | {runs} | {status} | {findings} |
|
||||
| DX Review | \\\`/plan-devex-review\\\` | Developer experience gaps | {runs} | {status} | {findings} |
|
||||
\\\`\\\`\\\`
|
||||
|
||||
Below the table, add these lines (omit any that are empty/not applicable):
|
||||
|
||||
Reference in New Issue
Block a user