@su-record/vibe 2.16.2 → 2.16.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/.env.example +15 -0
- package/README.en.md +1 -1
- package/README.md +1 -1
- package/agents/architect-low.md +1 -1
- package/agents/architect-medium.md +1 -1
- package/agents/architect.md +1 -1
- package/agents/build-error-resolver.md +1 -1
- package/agents/compounder.md +4 -0
- package/agents/diagrammer.md +4 -0
- package/agents/docs/api-documenter.md +1 -1
- package/agents/docs/changelog-writer.md +1 -1
- package/agents/e2e-tester.md +4 -0
- package/agents/explorer-low.md +1 -1
- package/agents/explorer-medium.md +1 -1
- package/agents/explorer.md +2 -2
- package/agents/figma/figma-analyst.md +4 -0
- package/agents/figma/figma-architect.md +4 -0
- package/agents/figma/figma-auditor.md +4 -0
- package/agents/figma/figma-builder.md +4 -0
- package/agents/implementer-low.md +1 -1
- package/agents/implementer-medium.md +1 -1
- package/agents/implementer.md +2 -2
- package/agents/planning/requirements-analyst.md +1 -1
- package/agents/planning/ux-advisor.md +1 -1
- package/agents/qa/acceptance-tester.md +1 -1
- package/agents/qa/edge-case-finder.md +1 -1
- package/agents/refactor-cleaner.md +1 -1
- package/agents/research/best-practices.md +1 -1
- package/agents/research/codebase-patterns.md +1 -1
- package/agents/research/framework-docs.md +1 -1
- package/agents/research/security-advisory.md +1 -1
- package/agents/searcher.md +1 -1
- package/agents/simplifier.md +4 -0
- package/agents/teams/debug-team.md +4 -0
- package/agents/teams/dev-team.md +4 -0
- package/agents/teams/docs-team.md +4 -0
- package/agents/teams/figma-team.md +4 -0
- package/agents/teams/fullstack-team.md +4 -0
- package/agents/teams/lite-team.md +4 -0
- package/agents/teams/migration-team.md +4 -0
- package/agents/teams/refactor-team.md +4 -0
- package/agents/teams/research-team.md +4 -0
- package/agents/teams/review-debate-team.md +4 -0
- package/agents/teams/security-team.md +4 -0
- package/agents/tester.md +2 -2
- package/agents/ui/ui-a11y-auditor.md +1 -1
- package/agents/ui/ui-antipattern-detector.md +1 -1
- package/agents/ui/ui-dataviz-advisor.md +1 -1
- package/agents/ui/ui-design-system-gen.md +1 -1
- package/agents/ui/ui-industry-analyzer.md +1 -1
- package/agents/ui/ui-layout-architect.md +1 -1
- package/agents/ui/ui-stack-implementer.md +1 -1
- package/agents/ui/ux-compliance-reviewer.md +1 -1
- package/agents/ui-previewer.md +4 -0
- package/dist/__tests__/agent-model-sync.test.d.ts +2 -0
- package/dist/__tests__/agent-model-sync.test.d.ts.map +1 -0
- package/dist/__tests__/agent-model-sync.test.js +71 -0
- package/dist/__tests__/agent-model-sync.test.js.map +1 -0
- package/dist/cli/auth.d.ts.map +1 -1
- package/dist/cli/auth.js +8 -1
- package/dist/cli/auth.js.map +1 -1
- package/dist/cli/commands/index.d.ts +1 -0
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +1 -0
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/info.d.ts.map +1 -1
- package/dist/cli/commands/info.js +2 -0
- package/dist/cli/commands/info.js.map +1 -1
- package/dist/cli/commands/llm.d.ts +11 -0
- package/dist/cli/commands/llm.d.ts.map +1 -0
- package/dist/cli/commands/llm.js +62 -0
- package/dist/cli/commands/llm.js.map +1 -0
- package/dist/cli/commands/setup.d.ts.map +1 -1
- package/dist/cli/commands/setup.js +35 -1
- package/dist/cli/commands/setup.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +59 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/llm/config.d.ts.map +1 -1
- package/dist/cli/llm/config.js +7 -0
- package/dist/cli/llm/config.js.map +1 -1
- package/dist/cli/llm/index.d.ts +1 -0
- package/dist/cli/llm/index.d.ts.map +1 -1
- package/dist/cli/llm/index.js +1 -0
- package/dist/cli/llm/index.js.map +1 -1
- package/dist/cli/llm/model-refresh.d.ts +29 -0
- package/dist/cli/llm/model-refresh.d.ts.map +1 -0
- package/dist/cli/llm/model-refresh.js +139 -0
- package/dist/cli/llm/model-refresh.js.map +1 -0
- package/dist/cli/llm/zai-commands.d.ts +15 -0
- package/dist/cli/llm/zai-commands.d.ts.map +1 -0
- package/dist/cli/llm/zai-commands.js +64 -0
- package/dist/cli/llm/zai-commands.js.map +1 -0
- package/dist/cli/postinstall/constants.d.ts.map +1 -1
- package/dist/cli/postinstall/constants.js +1 -0
- package/dist/cli/postinstall/constants.js.map +1 -1
- package/dist/cli/types.d.ts +15 -0
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/infra/lib/config/GlobalConfigManager.d.ts +2 -0
- package/dist/infra/lib/config/GlobalConfigManager.d.ts.map +1 -1
- package/dist/infra/lib/config/GlobalConfigManager.js +6 -0
- package/dist/infra/lib/config/GlobalConfigManager.js.map +1 -1
- package/dist/infra/lib/llm-availability.d.ts +4 -0
- package/dist/infra/lib/llm-availability.d.ts.map +1 -1
- package/dist/infra/lib/llm-availability.js +9 -1
- package/dist/infra/lib/llm-availability.js.map +1 -1
- package/dist/infra/lib/zai/client.d.ts +30 -0
- package/dist/infra/lib/zai/client.d.ts.map +1 -0
- package/dist/infra/lib/zai/client.js +124 -0
- package/dist/infra/lib/zai/client.js.map +1 -0
- package/dist/infra/lib/zai/index.d.ts +11 -0
- package/dist/infra/lib/zai/index.d.ts.map +1 -0
- package/dist/infra/lib/zai/index.js +10 -0
- package/dist/infra/lib/zai/index.js.map +1 -0
- package/dist/infra/lib/zai/orchestration.d.ts +15 -0
- package/dist/infra/lib/zai/orchestration.d.ts.map +1 -0
- package/dist/infra/lib/zai/orchestration.js +54 -0
- package/dist/infra/lib/zai/orchestration.js.map +1 -0
- package/dist/infra/lib/zai/types.d.ts +40 -0
- package/dist/infra/lib/zai/types.d.ts.map +1 -0
- package/dist/infra/lib/zai/types.js +5 -0
- package/dist/infra/lib/zai/types.js.map +1 -0
- package/dist/infra/orchestrator/SmartRouter.d.ts.map +1 -1
- package/dist/infra/orchestrator/SmartRouter.js +16 -34
- package/dist/infra/orchestrator/SmartRouter.js.map +1 -1
- package/dist/infra/orchestrator/index.d.ts +1 -0
- package/dist/infra/orchestrator/index.d.ts.map +1 -1
- package/dist/infra/orchestrator/index.js +11 -9
- package/dist/infra/orchestrator/index.js.map +1 -1
- package/dist/infra/orchestrator/types.d.ts +32 -9
- package/dist/infra/orchestrator/types.d.ts.map +1 -1
- package/dist/infra/orchestrator/types.js +68 -37
- package/dist/infra/orchestrator/types.js.map +1 -1
- package/dist/infra/orchestrator/types.test.d.ts +2 -0
- package/dist/infra/orchestrator/types.test.d.ts.map +1 -0
- package/dist/infra/orchestrator/types.test.js +57 -0
- package/dist/infra/orchestrator/types.test.js.map +1 -0
- package/hooks/scripts/__tests__/clone-behaviors.test.js +56 -0
- package/hooks/scripts/clone-extract.js +196 -11
- package/hooks/scripts/clone-spec.js +63 -12
- package/hooks/scripts/llm-orchestrate.js +49 -0
- package/package.json +4 -2
- package/skills/clone/SKILL.md +19 -4
- package/skills/create-prd/agents/edge-case-finder.md +4 -4
- package/skills/create-prd/agents/prioritizer.md +4 -4
- package/skills/create-prd/agents/requirements-writer.md +3 -3
- package/skills/create-prd/agents/researcher.md +3 -3
- package/skills/create-prd/agents/reviewer.md +3 -3
- package/skills/create-prd/orchestrator.md +2 -0
- package/skills/design-audit/orchestrator.md +2 -0
- package/skills/exec-plan/orchestrator.md +2 -0
- package/skills/parallel-research/orchestrator.md +2 -0
- package/skills/techdebt/orchestrator.md +2 -0
- package/skills/vibe.clone/SKILL.md +6 -3
- package/skills/vibe.llm/SKILL.md +50 -0
- package/vibe/rules/orchestrator-contract.md +56 -0
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
* screenshot.png — full-page screenshot
|
|
13
13
|
* stylesheets.json — @font-face + @keyframes harvested from all sheets
|
|
14
14
|
* states.json — non-default state rules (hover/focus/active/checked/tab/aria/data-state)
|
|
15
|
+
* behaviors.json — ACTIVE interaction sweep: scroll-triggered header/nav diffs +
|
|
16
|
+
* click-driven tab groups (content-swap detection). Captures JS-set
|
|
17
|
+
* state that static CSS harvesting can't see. Skip with --no-interact.
|
|
15
18
|
* asset-map.json — remote URL → local path mapping
|
|
16
19
|
* assets/images/*, assets/fonts/*
|
|
17
20
|
*
|
|
@@ -78,7 +81,7 @@ const CSS_PROPS = [
|
|
|
78
81
|
// ─── CLI parse ──────────────────────────────────────────────────────
|
|
79
82
|
function parseArgs(argv) {
|
|
80
83
|
const [, , cmd, urlArg, ...rest] = argv;
|
|
81
|
-
const opts = { stealth: false, ignoreRobots: false };
|
|
84
|
+
const opts = { stealth: false, ignoreRobots: false, interact: true };
|
|
82
85
|
for (const arg of rest) {
|
|
83
86
|
if (arg.startsWith('--out=')) opts.out = arg.slice(6);
|
|
84
87
|
else if (arg.startsWith('--viewport=')) opts.viewport = arg.slice(11);
|
|
@@ -86,6 +89,7 @@ function parseArgs(argv) {
|
|
|
86
89
|
else if (arg.startsWith('--wait=')) opts.wait = Number(arg.slice(7));
|
|
87
90
|
else if (arg === '--stealth') opts.stealth = true;
|
|
88
91
|
else if (arg === '--ignore-robots') opts.ignoreRobots = true;
|
|
92
|
+
else if (arg === '--no-interact') opts.interact = false;
|
|
89
93
|
}
|
|
90
94
|
return { cmd, url: urlArg, opts };
|
|
91
95
|
}
|
|
@@ -241,7 +245,7 @@ const PAGE_EXTRACT = `
|
|
|
241
245
|
// Deterministic: read state-dependent declarations straight from the stylesheets
|
|
242
246
|
// (no scripted clicking/hovering, so the output stays reproducible).
|
|
243
247
|
const propSet = new Set(props);
|
|
244
|
-
const STATE_RE = /:hover|:focus(?:-visible|-within)?|:active|:checked|:target|\\[aria-(?:expanded|selected|current|pressed)|\\[data-(?:state|active|open|selected)|\\[open\\]|\\.is-[a-z-]+|\\.(?:active|open|selected|expanded|current|show|visible)(?![\\w-])/i;
|
|
248
|
+
const STATE_RE = /:hover|:focus(?:-visible|-within)?|:active|:checked|:target|\\[aria-(?:expanded|selected|current|pressed)|\\[data-(?:state|active|open|selected)|\\[open\\]|\\.is-[a-z-]+|\\.has-[a-z-]+|\\.(?:active|open|selected|expanded|current|show|visible|scrolled|sticky|stuck|pinned|fixed|shrink|shrunk|compact|affix|headroom|scrolling)(?![\\w-])/i;
|
|
245
249
|
const harvestStateRule = (rule, media) => {
|
|
246
250
|
if (rule.selectorText && STATE_RE.test(rule.selectorText)) {
|
|
247
251
|
const decl = {};
|
|
@@ -556,6 +560,155 @@ async function freezeAnimations(page) {
|
|
|
556
560
|
});
|
|
557
561
|
}
|
|
558
562
|
|
|
563
|
+
// ─── Active interaction sweep ───────────────────────────────────────
|
|
564
|
+
// Static CSS harvesting (states.json) only sees declared :hover/:active/[data-state]
|
|
565
|
+
// rules. It is blind to JS-set state: a header that gains a class on scroll, a tab
|
|
566
|
+
// group that swaps content on click, Framer/GSAP inline-style animations. This sweep
|
|
567
|
+
// actually drives the page and diffs computed styles before/after — the #1 accuracy fix.
|
|
568
|
+
|
|
569
|
+
// Subset of props worth diffing for scroll/click state changes (kept small on purpose).
|
|
570
|
+
const BEHAVIOR_PROPS = [
|
|
571
|
+
'background-color', 'background-image', 'box-shadow', 'backdrop-filter',
|
|
572
|
+
'height', 'min-height', 'padding-top', 'padding-bottom',
|
|
573
|
+
'transform', 'opacity', 'position', 'top',
|
|
574
|
+
'border-bottom-width', 'border-bottom-color', 'color',
|
|
575
|
+
];
|
|
576
|
+
|
|
577
|
+
// Pure style differ — exported for testing. Returns { prop: { from, to } } for changed props.
|
|
578
|
+
function diffStyles(a, b) {
|
|
579
|
+
const out = {};
|
|
580
|
+
if (!a || !b) return out;
|
|
581
|
+
const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
582
|
+
for (const k of keys) {
|
|
583
|
+
const from = a[k] ?? null;
|
|
584
|
+
const to = b[k] ?? null;
|
|
585
|
+
if (from !== to && (from || to)) out[k] = { from, to };
|
|
586
|
+
}
|
|
587
|
+
return out;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// In-page: tag sticky/fixed/top-bar elements with a probe attr so we can re-find them.
|
|
591
|
+
const TAG_SCROLL_CANDIDATES = `(function () {
|
|
592
|
+
const els = Array.from(document.querySelectorAll(
|
|
593
|
+
'header, nav, [class*="header" i], [class*="nav" i], [class*="sticky" i], [class*="fixed" i]'
|
|
594
|
+
));
|
|
595
|
+
const out = []; let i = 0;
|
|
596
|
+
for (const el of els) {
|
|
597
|
+
if (i >= 6) break;
|
|
598
|
+
const cs = getComputedStyle(el);
|
|
599
|
+
const r = el.getBoundingClientRect();
|
|
600
|
+
const sticky = cs.position === 'fixed' || cs.position === 'sticky';
|
|
601
|
+
const topBar = r.top >= 0 && r.top < 120 && r.width > window.innerWidth * 0.5;
|
|
602
|
+
if (!sticky && !topBar) continue;
|
|
603
|
+
if (el.closest('[data-clone-probe]')) continue;
|
|
604
|
+
el.setAttribute('data-clone-probe', 'sc' + i);
|
|
605
|
+
const cls = (el.getAttribute('class') || '').trim().split(/\\s+/).filter(Boolean)[0] || '';
|
|
606
|
+
out.push({ probe: 'sc' + i, tag: el.tagName.toLowerCase(), cls });
|
|
607
|
+
i++;
|
|
608
|
+
}
|
|
609
|
+
return out;
|
|
610
|
+
})`;
|
|
611
|
+
|
|
612
|
+
const SNAP_PROBES = `(function (props) {
|
|
613
|
+
const out = {};
|
|
614
|
+
document.querySelectorAll('[data-clone-probe]').forEach((el) => {
|
|
615
|
+
const cs = getComputedStyle(el); const o = {};
|
|
616
|
+
for (const p of props) { const v = cs.getPropertyValue(p); if (v) o[p] = v.trim(); }
|
|
617
|
+
out[el.getAttribute('data-clone-probe')] = o;
|
|
618
|
+
});
|
|
619
|
+
return out;
|
|
620
|
+
})`;
|
|
621
|
+
|
|
622
|
+
// In-page: tag tab-like groups (≥2 siblings) so we can click them.
|
|
623
|
+
const TAG_TAB_GROUPS = `(function () {
|
|
624
|
+
const els = Array.from(document.querySelectorAll(
|
|
625
|
+
'[role="tab"], [aria-selected], button[class*="tab" i], li[class*="tab" i], [data-state="active"], [data-state="inactive"]'
|
|
626
|
+
));
|
|
627
|
+
const groups = new Map();
|
|
628
|
+
for (const el of els) {
|
|
629
|
+
const p = el.parentElement; if (!p) continue;
|
|
630
|
+
if (!groups.has(p)) groups.set(p, []);
|
|
631
|
+
groups.get(p).push(el);
|
|
632
|
+
}
|
|
633
|
+
const out = []; let g = 0;
|
|
634
|
+
for (const [, items] of groups) {
|
|
635
|
+
if (g >= 4) break;
|
|
636
|
+
if (items.length < 2) continue;
|
|
637
|
+
items.forEach((el, idx) => el.setAttribute('data-clone-tab', g + '_' + idx));
|
|
638
|
+
out.push({ group: g, count: items.length, labels: items.map((el) => (el.textContent || '').trim().slice(0, 40)).filter(Boolean) });
|
|
639
|
+
g++;
|
|
640
|
+
}
|
|
641
|
+
return out;
|
|
642
|
+
})`;
|
|
643
|
+
|
|
644
|
+
// Fingerprint the visible text near a tab group, to detect content swaps on click.
|
|
645
|
+
const TAB_CONTENT_FP = `(function (g) {
|
|
646
|
+
const first = document.querySelector('[data-clone-tab="' + g + '_0"]');
|
|
647
|
+
if (!first) return null;
|
|
648
|
+
const box = first.closest('section, main, div');
|
|
649
|
+
const root = box ? (box.parentElement || box) : document.body;
|
|
650
|
+
return (root.innerText || '').replace(/\\s+/g, ' ').trim().slice(0, 4000);
|
|
651
|
+
})`;
|
|
652
|
+
|
|
653
|
+
const wait = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
654
|
+
|
|
655
|
+
async function runInteractionSweep(page) {
|
|
656
|
+
const behaviors = { scroll: [], interactive: [] };
|
|
657
|
+
|
|
658
|
+
// ── Scroll-triggered state (header/nav shrink, background, shadow on scroll) ──
|
|
659
|
+
const candidates = await page.evaluate(`(${TAG_SCROLL_CANDIDATES})()`);
|
|
660
|
+
if (candidates.length) {
|
|
661
|
+
const before = await page.evaluate(`(${SNAP_PROBES})(${JSON.stringify(BEHAVIOR_PROPS)})`);
|
|
662
|
+
const innerH = await page.evaluate('window.innerHeight');
|
|
663
|
+
const triggerY = Math.min(700, Math.max(200, Math.round(innerH * 0.9)));
|
|
664
|
+
await page.evaluate(`window.scrollTo(0, ${triggerY})`);
|
|
665
|
+
await wait(500);
|
|
666
|
+
const after = await page.evaluate(`(${SNAP_PROBES})(${JSON.stringify(BEHAVIOR_PROPS)})`);
|
|
667
|
+
await page.evaluate('window.scrollTo(0, 0)');
|
|
668
|
+
await wait(300);
|
|
669
|
+
for (const c of candidates) {
|
|
670
|
+
const changed = diffStyles(before[c.probe], after[c.probe]);
|
|
671
|
+
if (Object.keys(changed).length) {
|
|
672
|
+
behaviors.scroll.push({
|
|
673
|
+
label: c.cls ? `${c.tag}.${c.cls}` : c.tag,
|
|
674
|
+
tag: c.tag, cls: c.cls, triggerScrollY: triggerY, changed,
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// ── Click-driven tab groups (content swap detection) ──
|
|
681
|
+
const groups = await page.evaluate(`(${TAG_TAB_GROUPS})()`);
|
|
682
|
+
for (const grp of groups) {
|
|
683
|
+
try {
|
|
684
|
+
const beforeFp = await page.evaluate(`(${TAB_CONTENT_FP})(${grp.group})`);
|
|
685
|
+
const clicked = await page.evaluate(
|
|
686
|
+
`(function (g) { const t = document.querySelector('[data-clone-tab="' + g + '_1"]'); if (!t) return false; t.click(); return true; })(${grp.group})`
|
|
687
|
+
);
|
|
688
|
+
if (!clicked) continue;
|
|
689
|
+
await wait(450);
|
|
690
|
+
const afterFp = await page.evaluate(`(${TAB_CONTENT_FP})(${grp.group})`);
|
|
691
|
+
// restore default state
|
|
692
|
+
await page.evaluate(
|
|
693
|
+
`(function (g) { const t = document.querySelector('[data-clone-tab="' + g + '_0"]'); if (t) t.click(); })(${grp.group})`
|
|
694
|
+
);
|
|
695
|
+
await wait(200);
|
|
696
|
+
behaviors.interactive.push({
|
|
697
|
+
kind: 'tab-group',
|
|
698
|
+
count: grp.count,
|
|
699
|
+
tabLabels: grp.labels,
|
|
700
|
+
contentSwapsOnClick: beforeFp != null && afterFp != null && beforeFp !== afterFp,
|
|
701
|
+
});
|
|
702
|
+
} catch { /* one bad group must not abort the whole sweep */ }
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// Clean up probe attributes BEFORE static extraction so they don't pollute output.
|
|
706
|
+
await page.evaluate(
|
|
707
|
+
'document.querySelectorAll("[data-clone-probe],[data-clone-tab]").forEach((e) => { e.removeAttribute("data-clone-probe"); e.removeAttribute("data-clone-tab"); })'
|
|
708
|
+
);
|
|
709
|
+
return behaviors;
|
|
710
|
+
}
|
|
711
|
+
|
|
559
712
|
// HTML sanitization: strip scripts/analytics before saving
|
|
560
713
|
function sanitizeHtml(html) {
|
|
561
714
|
return html
|
|
@@ -617,6 +770,17 @@ async function capture({ url, opts }) {
|
|
|
617
770
|
if (opts.wait) await new Promise((r) => setTimeout(r, opts.wait));
|
|
618
771
|
else await new Promise((r) => setTimeout(r, 1200));
|
|
619
772
|
|
|
773
|
+
// Active interaction sweep (before freezing — it observes real transitions/JS state).
|
|
774
|
+
let behaviors = null;
|
|
775
|
+
if (opts.interact) {
|
|
776
|
+
try {
|
|
777
|
+
behaviors = await runInteractionSweep(page);
|
|
778
|
+
console.log(`[clone-extract] interaction sweep: ${behaviors.scroll.length} scroll-state, ${behaviors.interactive.length} tab-group(s)`);
|
|
779
|
+
} catch (e) {
|
|
780
|
+
console.log(`[clone-extract] interaction sweep skipped: ${e.message}`);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
620
784
|
await freezeAnimations(page);
|
|
621
785
|
|
|
622
786
|
// PAGE_EXTRACT is a parenthesized function-expression string. page.evaluate treats a
|
|
@@ -731,6 +895,22 @@ async function capture({ url, opts }) {
|
|
|
731
895
|
}, null, 2),
|
|
732
896
|
);
|
|
733
897
|
fs.writeFileSync(path.join(opts.out, 'asset-map.json'), JSON.stringify(assetMap, null, 2));
|
|
898
|
+
if (behaviors) {
|
|
899
|
+
// Rewrite any remote asset URLs captured in scroll-state background-image diffs.
|
|
900
|
+
for (const s of behaviors.scroll) {
|
|
901
|
+
for (const ch of Object.values(s.changed)) {
|
|
902
|
+
if (typeof ch.from === 'string') ch.from = rewriteCssValue(ch.from);
|
|
903
|
+
if (typeof ch.to === 'string') ch.to = rewriteCssValue(ch.to);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
fs.writeFileSync(
|
|
907
|
+
path.join(opts.out, 'behaviors.json'),
|
|
908
|
+
JSON.stringify({
|
|
909
|
+
meta: { url, bp: opts.bp || null, capturedAt: new Date().toISOString() },
|
|
910
|
+
...behaviors,
|
|
911
|
+
}, null, 2),
|
|
912
|
+
);
|
|
913
|
+
}
|
|
734
914
|
|
|
735
915
|
const okCount = Object.values(assetMap).filter((a) => a.status === 'ok').length;
|
|
736
916
|
console.log(`[clone-extract] done → ${opts.out}`);
|
|
@@ -742,15 +922,20 @@ async function capture({ url, opts }) {
|
|
|
742
922
|
}
|
|
743
923
|
|
|
744
924
|
// ─── Entry ──────────────────────────────────────────────────────────
|
|
745
|
-
const
|
|
925
|
+
const isMain = process.argv[1] && process.argv[1].replace(/\\/g, '/').endsWith('clone-extract.js');
|
|
926
|
+
if (isMain) {
|
|
927
|
+
const { cmd, url, opts } = parseArgs(process.argv);
|
|
928
|
+
|
|
929
|
+
if (cmd !== 'capture') {
|
|
930
|
+
console.error('Usage: node clone-extract.js capture <URL> --out=<dir> --viewport=WxH[@DPR] --bp=mo|pc [--stealth] [--ignore-robots] [--no-interact] [--wait=ms]');
|
|
931
|
+
process.exit(1);
|
|
932
|
+
}
|
|
746
933
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
934
|
+
capture({ url, opts }).catch((err) => {
|
|
935
|
+
console.error(`[clone-extract] FAIL: ${err.message}`);
|
|
936
|
+
if (process.env.DEBUG) console.error(err.stack);
|
|
937
|
+
process.exit(1);
|
|
938
|
+
});
|
|
750
939
|
}
|
|
751
940
|
|
|
752
|
-
|
|
753
|
-
console.error(`[clone-extract] FAIL: ${err.message}`);
|
|
754
|
-
if (process.env.DEBUG) console.error(err.stack);
|
|
755
|
-
process.exit(1);
|
|
756
|
-
});
|
|
941
|
+
export { diffStyles };
|
|
@@ -27,15 +27,12 @@ function parseArgs(argv) {
|
|
|
27
27
|
if (a.startsWith('--out=')) opts.out = a.slice(6);
|
|
28
28
|
else if (a.startsWith('--section=')) opts.section = a.slice(10);
|
|
29
29
|
else if (a.startsWith('--feature=')) opts.feature = a.slice(10);
|
|
30
|
+
else if (a.startsWith('--behaviors=')) opts.behaviors = a.slice(12);
|
|
30
31
|
}
|
|
31
32
|
return { sectionsPath, opts };
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
const { sectionsPath, opts } = parseArgs(process.argv);
|
|
35
|
-
if (!sectionsPath || !opts.out) {
|
|
36
|
-
console.error('Usage: node clone-spec.js <sections.json> --out=<dir> [--section=Name] [--feature=name]');
|
|
37
|
-
process.exit(1);
|
|
38
|
-
}
|
|
39
36
|
|
|
40
37
|
// ─── Helpers ────────────────────────────────────────────────────────
|
|
41
38
|
function safeName(name) {
|
|
@@ -95,6 +92,40 @@ function componentsBlock(components) {
|
|
|
95
92
|
}).join('\n');
|
|
96
93
|
}
|
|
97
94
|
|
|
95
|
+
// True if any node in the section subtree matches a behavior's tag (+ optional class).
|
|
96
|
+
function sectionHasNode(section, tag, cls) {
|
|
97
|
+
const hit = (n) => {
|
|
98
|
+
if (n.tag === tag && (!cls || (n.classes || '').split(/\s+/).includes(cls))) return true;
|
|
99
|
+
return (n.children || []).some(hit);
|
|
100
|
+
};
|
|
101
|
+
return (section.children || []).some(hit);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function behaviorsBlock(section, behaviors) {
|
|
105
|
+
if (!behaviors) return null;
|
|
106
|
+
const scroll = (behaviors.scroll || []).filter((b) => sectionHasNode(section, b.tag, b.cls));
|
|
107
|
+
// Tab groups aren't tag-keyed; attach to any section that contains a [role=tab]-ish node.
|
|
108
|
+
const tabs = (behaviors.interactive || []).filter(() =>
|
|
109
|
+
sectionHasNode(section, 'button') || sectionHasNode(section, 'li') || sectionHasNode(section, 'a'),
|
|
110
|
+
);
|
|
111
|
+
if (!scroll.length && !tabs.length) return null;
|
|
112
|
+
|
|
113
|
+
const lines = [];
|
|
114
|
+
for (const b of scroll) {
|
|
115
|
+
const diffs = Object.entries(b.changed)
|
|
116
|
+
.map(([k, v]) => ` - ${k}: \`${v.from}\` → \`${v.to}\``).join('\n');
|
|
117
|
+
lines.push(`- **Scroll-triggered** on \`${b.label}\` (past ~${b.triggerScrollY}px scroll):\n${diffs}`);
|
|
118
|
+
}
|
|
119
|
+
for (const t of tabs) {
|
|
120
|
+
const labels = (t.tabLabels || []).map((l) => `"${l}"`).join(', ') || '(unlabeled)';
|
|
121
|
+
const swap = t.contentSwapsOnClick
|
|
122
|
+
? '**content SWAPS on click** → extract every tab\'s content/assets, build click-driven'
|
|
123
|
+
: 'no content swap detected (styling-only tabs)';
|
|
124
|
+
lines.push(`- **Tab group** ×${t.count} [${labels}]: ${swap}`);
|
|
125
|
+
}
|
|
126
|
+
return lines.join('\n');
|
|
127
|
+
}
|
|
128
|
+
|
|
98
129
|
function assetsBlock(images) {
|
|
99
130
|
const bg = (images && images.bg) || [];
|
|
100
131
|
const content = (images && images.content) || [];
|
|
@@ -105,12 +136,16 @@ function assetsBlock(images) {
|
|
|
105
136
|
}
|
|
106
137
|
|
|
107
138
|
// ─── Spec rendering ─────────────────────────────────────────────────
|
|
108
|
-
function renderSpec(section, meta) {
|
|
139
|
+
function renderSpec(section, meta, behaviors) {
|
|
109
140
|
const box = section.box || {};
|
|
110
141
|
const texts = collectText(section);
|
|
111
142
|
const textBlock = texts.length
|
|
112
143
|
? texts.map((t) => `- "${t}"`).join('\n')
|
|
113
144
|
: '- (no direct text — likely visual/asset-only section)';
|
|
145
|
+
const behavior = behaviorsBlock(section, behaviors);
|
|
146
|
+
const behaviorSection = behavior
|
|
147
|
+
? `\n## Dynamic behaviors — observed by ACTIVE capture (not guesses)\n_Captured by driving the live page (scroll/click). These override the static heuristic above._\n${behavior}\n`
|
|
148
|
+
: '';
|
|
114
149
|
return `# ${section.name} — Clone Spec (${meta.bp})
|
|
115
150
|
|
|
116
151
|
> Auto-generated skeleton from sections.json by clone-spec.js. **This is a build contract.**
|
|
@@ -123,7 +158,7 @@ function renderSpec(section, meta) {
|
|
|
123
158
|
|
|
124
159
|
## Interaction model — ⚠ confirm before building
|
|
125
160
|
${interactionBlock(section.interaction)}
|
|
126
|
-
|
|
161
|
+
${behaviorSection}
|
|
127
162
|
## States to reproduce
|
|
128
163
|
_Harvested non-default state rules (hover/focus/active/checked/tab/aria/data-state). Implement each._
|
|
129
164
|
${statesBlock(section.states)}
|
|
@@ -162,6 +197,13 @@ function main() {
|
|
|
162
197
|
viewport: data.meta && data.meta.viewport,
|
|
163
198
|
bp: opts.section ? (data.meta && data.meta.bp) : (data.meta && data.meta.bp) || '?',
|
|
164
199
|
};
|
|
200
|
+
// behaviors.json (active interaction sweep) lives next to the source computed.json.
|
|
201
|
+
const behaviorsPath = opts.behaviors || path.join(path.dirname(sectionsPath), 'behaviors.json');
|
|
202
|
+
let behaviors = null;
|
|
203
|
+
if (fs.existsSync(behaviorsPath)) {
|
|
204
|
+
try { behaviors = JSON.parse(fs.readFileSync(behaviorsPath, 'utf8')); } catch { behaviors = null; }
|
|
205
|
+
}
|
|
206
|
+
|
|
165
207
|
let sections = data.sections || [];
|
|
166
208
|
if (opts.section) sections = sections.filter((s) => s.name === opts.section);
|
|
167
209
|
if (!sections.length) {
|
|
@@ -172,15 +214,24 @@ function main() {
|
|
|
172
214
|
fs.mkdirSync(opts.out, { recursive: true });
|
|
173
215
|
for (const section of sections) {
|
|
174
216
|
const file = path.join(opts.out, `${safeName(section.name)}.spec.md`);
|
|
175
|
-
fs.writeFileSync(file, renderSpec(section, meta));
|
|
217
|
+
fs.writeFileSync(file, renderSpec(section, meta, behaviors));
|
|
176
218
|
console.log(`[clone-spec] wrote ${file}`);
|
|
177
219
|
}
|
|
178
220
|
console.log(`[clone-spec] done → ${sections.length} spec(s) in ${opts.out}`);
|
|
179
221
|
}
|
|
180
222
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
223
|
+
const isMain = process.argv[1] && process.argv[1].replace(/\\/g, '/').endsWith('clone-spec.js');
|
|
224
|
+
if (isMain) {
|
|
225
|
+
if (!sectionsPath || !opts.out) {
|
|
226
|
+
console.error('Usage: node clone-spec.js <sections.json> --out=<dir> [--section=Name] [--feature=name] [--behaviors=path]');
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
229
|
+
try { main(); }
|
|
230
|
+
catch (e) {
|
|
231
|
+
console.error(`[clone-spec] FAIL: ${e.message}`);
|
|
232
|
+
if (process.env.DEBUG) console.error(e.stack);
|
|
233
|
+
process.exit(1);
|
|
234
|
+
}
|
|
186
235
|
}
|
|
236
|
+
|
|
237
|
+
export { sectionHasNode, behaviorsBlock };
|
|
@@ -105,6 +105,7 @@ function resolveModel(providerName, config) {
|
|
|
105
105
|
if (providerName === 'gpt-codex') return config.models?.gptCodex || 'gpt-5.3-codex';
|
|
106
106
|
if (providerName === 'gpt') return config.models?.gpt || 'gpt-5.5';
|
|
107
107
|
if (providerName === 'antigravity') return config.models?.antigravity || 'antigravity';
|
|
108
|
+
if (providerName === 'zai') return config.models?.zaiCoding || config.models?.zai || 'glm-5.2';
|
|
108
109
|
if (providerName === 'claude') return 'claude';
|
|
109
110
|
return providerName;
|
|
110
111
|
}
|
|
@@ -271,6 +272,46 @@ function callCodexCli(prompt, sysPrompt, jsonMode, model, timeoutMs) {
|
|
|
271
272
|
});
|
|
272
273
|
}
|
|
273
274
|
|
|
275
|
+
// ZAI (Z.ai / GLM) — OpenAI 호환 HTTP API (CLI 없음)
|
|
276
|
+
const ZAI_BASE_GENERAL = 'https://api.z.ai/api/paas/v4';
|
|
277
|
+
const ZAI_BASE_CODING = 'https://api.z.ai/api/coding/paas/v4';
|
|
278
|
+
|
|
279
|
+
function callZaiApi(prompt, sysPrompt, jsonMode, model, timeoutMs) {
|
|
280
|
+
const vibeConfig = readVibeConfig();
|
|
281
|
+
const codingKey = vibeConfig.credentials?.zai?.codingApiKey || process.env.ZAI_CODING_API_KEY;
|
|
282
|
+
const generalKey = vibeConfig.credentials?.zai?.apiKey || process.env.ZAI_API_KEY;
|
|
283
|
+
const useCoding = Boolean(codingKey);
|
|
284
|
+
const apiKey = codingKey || generalKey;
|
|
285
|
+
if (!apiKey) return Promise.reject(new Error('ZAI API key not set (vibe zai coding-key <key>)'));
|
|
286
|
+
|
|
287
|
+
const base = useCoding ? ZAI_BASE_CODING : ZAI_BASE_GENERAL;
|
|
288
|
+
const system = sysPrompt && sysPrompt !== DEFAULT_SYSTEM_PROMPT ? sysPrompt : DEFAULT_SYSTEM_PROMPT;
|
|
289
|
+
const content = jsonMode ? `${prompt}\n\nRespond with valid JSON only.` : prompt;
|
|
290
|
+
const controller = new AbortController();
|
|
291
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs || CLI_TIMEOUT_MS);
|
|
292
|
+
|
|
293
|
+
return fetch(`${base}/chat/completions`, {
|
|
294
|
+
method: 'POST',
|
|
295
|
+
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },
|
|
296
|
+
body: JSON.stringify({
|
|
297
|
+
model,
|
|
298
|
+
messages: [{ role: 'system', content: system }, { role: 'user', content }],
|
|
299
|
+
max_tokens: 4096,
|
|
300
|
+
temperature: 0,
|
|
301
|
+
}),
|
|
302
|
+
signal: controller.signal,
|
|
303
|
+
}).then(async (res) => {
|
|
304
|
+
if (!res.ok) {
|
|
305
|
+
const body = await res.text().catch(() => '');
|
|
306
|
+
throw new Error(`zai api failed (code ${res.status}): ${body.slice(0, 500)}`);
|
|
307
|
+
}
|
|
308
|
+
const data = await res.json();
|
|
309
|
+
const text = data?.choices?.[0]?.message?.content?.trim();
|
|
310
|
+
if (!text) throw new Error('zai api returned empty response');
|
|
311
|
+
return text;
|
|
312
|
+
}).finally(() => clearTimeout(timer));
|
|
313
|
+
}
|
|
314
|
+
|
|
274
315
|
function callAntigravityCli(prompt, sysPrompt, jsonMode, timeoutMs) {
|
|
275
316
|
const fullPrompt = buildCliPrompt(prompt, sysPrompt, jsonMode);
|
|
276
317
|
const args = ['-p', fullPrompt];
|
|
@@ -357,6 +398,14 @@ async function callProvider(providerName, prompt, sysPrompt, jsonMode, timeoutMs
|
|
|
357
398
|
return await callAntigravityCli(prompt, sysPrompt, jsonMode, timeoutMs);
|
|
358
399
|
}
|
|
359
400
|
|
|
401
|
+
if (providerName === 'zai') {
|
|
402
|
+
// coding 키가 있으면 coding plan flagship(5.2), 아니면 일반 API 상한(5.1)
|
|
403
|
+
const hasCoding = Boolean(vibeConfig.credentials?.zai?.codingApiKey || process.env.ZAI_CODING_API_KEY);
|
|
404
|
+
const fallback = hasCoding ? 'glm-5.2' : 'glm-5.1';
|
|
405
|
+
const model = vibeConfig.models?.zaiCoding || vibeConfig.models?.zai || process.env.ZAI_MODEL || fallback;
|
|
406
|
+
return await callZaiApi(prompt, sysPrompt, jsonMode, model, timeoutMs);
|
|
407
|
+
}
|
|
408
|
+
|
|
360
409
|
if (providerName === 'claude') {
|
|
361
410
|
return await callClaudeCli(prompt, sysPrompt, jsonMode, timeoutMs);
|
|
362
411
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@su-record/vibe",
|
|
3
|
-
"version": "2.16.
|
|
4
|
-
"description": "AI Coding Framework for Claude Code — 42+ agents,
|
|
3
|
+
"version": "2.16.4",
|
|
4
|
+
"description": "AI Coding Framework for Claude Code — 42+ agents, 73 skills, multi-LLM orchestration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/cli/index.js",
|
|
7
7
|
"exports": {
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
"dev": "tsc --watch",
|
|
28
28
|
"gen:skill-docs": "npx tsx scripts/gen-skill-docs.ts",
|
|
29
29
|
"gen:skill-docs:check": "npx tsx scripts/gen-skill-docs.ts --check",
|
|
30
|
+
"sync:agent-models": "npx tsx scripts/sync-agent-models.ts",
|
|
31
|
+
"sync:agent-models:check": "npx tsx scripts/sync-agent-models.ts --check",
|
|
30
32
|
"validate:skill-invocation": "npx tsx scripts/validate-skill-invocation.ts",
|
|
31
33
|
"validate:counts": "npx tsx scripts/validate-counts.ts",
|
|
32
34
|
"test": "vitest run",
|
package/skills/clone/SKILL.md
CHANGED
|
@@ -41,6 +41,11 @@ The rendered DOM is the source of truth for markup. Screenshots are for pixel ve
|
|
|
41
41
|
|
|
42
42
|
6. Do NOT ship default-state-only. Implement every harvested state (hover/focus/active/open/
|
|
43
43
|
tab-switch) from states.json / the section spec.
|
|
44
|
+
|
|
45
|
+
7. Do NOT ignore behaviors.json. The ACTIVE interaction sweep (scroll-state diffs +
|
|
46
|
+
click-driven tab content-swap detection) catches JS-set state that static CSS harvesting
|
|
47
|
+
is blind to. When the spec's "Dynamic behaviors" block conflicts with the static
|
|
48
|
+
interaction heuristic, the active capture wins.
|
|
44
49
|
```
|
|
45
50
|
|
|
46
51
|
## Full Flow
|
|
@@ -119,6 +124,7 @@ node {{VIBE_PATH}}/hooks/scripts/clone-extract.js capture <URL> \
|
|
|
119
124
|
├── computed.json — per-element computed CSS + bounding box
|
|
120
125
|
├── screenshot.png — full-page screenshot
|
|
121
126
|
├── states.json — non-default state rules (hover/focus/active/checked/tab/aria/data-state)
|
|
127
|
+
├── behaviors.json — ACTIVE sweep: scroll-triggered header/nav diffs + click-driven tab groups
|
|
122
128
|
├── assets/
|
|
123
129
|
│ ├── images/ — all <img>, background-image, <picture> sources
|
|
124
130
|
│ └── fonts/ — @font-face srcs
|
|
@@ -136,8 +142,14 @@ node {{VIBE_PATH}}/hooks/scripts/clone-extract.js capture <URL> \
|
|
|
136
142
|
6. Rewrite asset URLs in rendered.html and computed.json to local paths
|
|
137
143
|
7. Strip inline analytics/tracking scripts before saving rendered.html
|
|
138
144
|
8. Harvest non-default state rules from all stylesheets → states.json
|
|
139
|
-
(deterministic: read :hover/:focus/:active/:checked/[aria-*]/[data-state]/.is-*/.active
|
|
140
|
-
declarations straight from CSS — NO scripted clicking
|
|
145
|
+
(deterministic: read :hover/:focus/:active/:checked/[aria-*]/[data-state]/.is-*/.active/
|
|
146
|
+
.scrolled/.sticky/.pinned/… declarations straight from CSS — NO scripted clicking)
|
|
147
|
+
9. ACTIVE interaction sweep → behaviors.json (runs after lazy-load, before freeze).
|
|
148
|
+
Drives the live page to capture JS-set state that CSS harvesting can't see:
|
|
149
|
+
- Scroll-state diff: tag sticky/fixed/top-bar headers+nav, snapshot computed CSS at
|
|
150
|
+
scroll 0, scroll past threshold, re-snapshot, diff → {prop: {from, to}, triggerScrollY}
|
|
151
|
+
- Tab groups: click tab-like sibling sets, detect whether content swaps on click
|
|
152
|
+
Disable with --no-interact (restores the old fully-deterministic, screenshot-stable capture).
|
|
141
153
|
```
|
|
142
154
|
|
|
143
155
|
---
|
|
@@ -256,7 +268,9 @@ node {{VIBE_PATH}}/hooks/scripts/clone-validate.js \
|
|
|
256
268
|
```
|
|
257
269
|
|
|
258
270
|
⛔ **No section is built without a completed spec.** Step 0 emits `_specs/{Section}.spec.md`
|
|
259
|
-
(interaction model + states + computed CSS + assets +
|
|
271
|
+
(interaction model + **active-capture Dynamic behaviors** + states + computed CSS + assets +
|
|
272
|
+
text + checklist). clone-spec.js auto-loads `behaviors.json` from the sections.json dir and
|
|
273
|
+
attaches matching scroll/tab behaviors per section. Before writing a
|
|
260
274
|
section's component, Claude reviews its spec and resolves every `TODO` (confirm interaction
|
|
261
275
|
model, list states to implement, choose tags, replace copyrighted text). The spec is the
|
|
262
276
|
contract AND the audit trail — it forces extraction rigor before any code is written.
|
|
@@ -407,4 +421,5 @@ Claude must:
|
|
|
407
421
|
| clone-refine.js produces empty sections | Site likely uses Shadow DOM or canvas rendering. Report and ask whether to fall back to screenshot-only mode. |
|
|
408
422
|
| Pixel diff stuck > 0.05 after 5 rounds | Likely font fallback or anti-aliasing. Report metric, allow user to accept threshold. |
|
|
409
423
|
| Interaction model guess wrong (Phase 5) | section.interaction is a static-DOM heuristic. Re-observe the live site, correct the model in the spec, rebuild the section for the confirmed model. |
|
|
410
|
-
| states.json empty but site has hover/tabs | States may be set via inline JS, not CSS rules.
|
|
424
|
+
| states.json empty but site has hover/tabs | States may be set via inline JS, not CSS rules. Check behaviors.json (active sweep captures scroll/tab JS state). If still missing, note in the spec and capture manually during Phase 5. |
|
|
425
|
+
| behaviors.json missing or empty | Active sweep was disabled (--no-interact), hit no sticky/tab elements, or errored (logged, non-fatal). Falls back to static states.json. Re-run without --no-interact to retry. |
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: edge-case-finder
|
|
2
|
+
name: prd-edge-case-finder
|
|
3
3
|
role: Identifies edge cases, constraints, and risks for each requirement
|
|
4
4
|
tools: [Read, WebSearch]
|
|
5
5
|
---
|
|
@@ -17,8 +17,8 @@ Stress-tests the requirements by systematically exploring what happens at bounda
|
|
|
17
17
|
- Flag security concerns: injection, over-fetching, authorization bypass
|
|
18
18
|
|
|
19
19
|
## Input
|
|
20
|
-
- User stories and acceptance criteria from requirements-writer
|
|
21
|
-
- Research brief from researcher (constraints section)
|
|
20
|
+
- User stories and acceptance criteria from prd-requirements-writer
|
|
21
|
+
- Research brief from prd-researcher (constraints section)
|
|
22
22
|
|
|
23
23
|
## Output
|
|
24
24
|
Edge cases and risks appended to each user story, plus a global risks section:
|
|
@@ -41,7 +41,7 @@ Edge cases and risks appended to each user story, plus a global risks section:
|
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
## Communication
|
|
44
|
-
- Reports findings to: prioritizer, reviewer
|
|
44
|
+
- Reports findings to: prd-prioritizer, prd-reviewer
|
|
45
45
|
- Receives instructions from: orchestrator (create-prd skill)
|
|
46
46
|
|
|
47
47
|
## Domain Knowledge
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: prioritizer
|
|
2
|
+
name: prd-prioritizer
|
|
3
3
|
role: Applies RICE and MoSCoW scoring to rank requirements for implementation
|
|
4
4
|
tools: [Read]
|
|
5
5
|
---
|
|
@@ -17,8 +17,8 @@ Applies structured scoring frameworks to the full requirements list to produce a
|
|
|
17
17
|
- Document prioritization rationale so it can be revisited
|
|
18
18
|
|
|
19
19
|
## Input
|
|
20
|
-
- User stories from requirements-writer
|
|
21
|
-
- Edge cases from edge-case-finder
|
|
20
|
+
- User stories from prd-requirements-writer
|
|
21
|
+
- Edge cases from prd-edge-case-finder
|
|
22
22
|
- Optional: team velocity or sprint capacity
|
|
23
23
|
|
|
24
24
|
## Output
|
|
@@ -53,7 +53,7 @@ US-01 must ship before US-02 (filter requires results to exist)
|
|
|
53
53
|
```
|
|
54
54
|
|
|
55
55
|
## Communication
|
|
56
|
-
- Reports findings to: reviewer
|
|
56
|
+
- Reports findings to: prd-reviewer
|
|
57
57
|
- Receives instructions from: orchestrator (create-prd skill)
|
|
58
58
|
|
|
59
59
|
## Domain Knowledge
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: requirements-writer
|
|
2
|
+
name: prd-requirements-writer
|
|
3
3
|
role: Writes user stories and acceptance criteria from research brief
|
|
4
4
|
tools: [Read, Write]
|
|
5
5
|
---
|
|
@@ -17,7 +17,7 @@ Translates research findings and user goals into structured user stories with cl
|
|
|
17
17
|
- Avoid implementation details — describe behavior, not code
|
|
18
18
|
|
|
19
19
|
## Input
|
|
20
|
-
- Research brief from researcher
|
|
20
|
+
- Research brief from prd-researcher
|
|
21
21
|
- User personas or target segment description
|
|
22
22
|
- Optional: existing SPEC or feature file for context
|
|
23
23
|
|
|
@@ -41,7 +41,7 @@ so that I can find items without browsing all categories.
|
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
## Communication
|
|
44
|
-
- Reports findings to: edge-case-finder, prioritizer, reviewer
|
|
44
|
+
- Reports findings to: prd-edge-case-finder, prd-prioritizer, prd-reviewer
|
|
45
45
|
- Receives instructions from: orchestrator (create-prd skill)
|
|
46
46
|
|
|
47
47
|
## Domain Knowledge
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: researcher
|
|
2
|
+
name: prd-researcher
|
|
3
3
|
role: Gathers market context and user needs using web search and codebase analysis
|
|
4
4
|
tools: [WebSearch, Read, Glob, Grep, Bash]
|
|
5
5
|
---
|
|
@@ -14,7 +14,7 @@ Gathers external context — competitor patterns, industry standards, user expec
|
|
|
14
14
|
- Identify common user expectations and mental models for this feature type
|
|
15
15
|
- Review existing codebase for related patterns, conventions, or prior art
|
|
16
16
|
- Surface relevant regulatory, accessibility, or compliance constraints
|
|
17
|
-
- Compile source references so requirements-writer can cite evidence
|
|
17
|
+
- Compile source references so prd-requirements-writer can cite evidence
|
|
18
18
|
|
|
19
19
|
## Input
|
|
20
20
|
- Feature or problem description from the user
|
|
@@ -48,7 +48,7 @@ Research brief:
|
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
## Communication
|
|
51
|
-
- Reports findings to: requirements-writer, edge-case-finder
|
|
51
|
+
- Reports findings to: prd-requirements-writer, prd-edge-case-finder
|
|
52
52
|
- Receives instructions from: orchestrator (create-prd skill)
|
|
53
53
|
|
|
54
54
|
## Domain Knowledge
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: reviewer
|
|
2
|
+
name: prd-reviewer
|
|
3
3
|
role: Reviews PRD completeness and testability before sign-off
|
|
4
4
|
tools: [Read]
|
|
5
5
|
---
|
|
@@ -18,8 +18,8 @@ Final quality gate for the PRD. Checks that every requirement is testable, every
|
|
|
18
18
|
|
|
19
19
|
## Input
|
|
20
20
|
- Complete PRD draft assembled from all agents
|
|
21
|
-
- Edge cases from edge-case-finder
|
|
22
|
-
- Prioritized backlog from prioritizer
|
|
21
|
+
- Edge cases from prd-edge-case-finder
|
|
22
|
+
- Prioritized backlog from prd-prioritizer
|
|
23
23
|
|
|
24
24
|
## Output
|
|
25
25
|
PRD review report:
|