@sun-asterisk/sungen 2.7.0-beta.0 → 3.0.0-beta.71
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/README.md +1 -1
- package/dist/cli/commands/add.js +3 -3
- package/dist/cli/commands/add.js.map +1 -1
- package/dist/cli/commands/audit.d.ts +3 -0
- package/dist/cli/commands/audit.d.ts.map +1 -0
- package/dist/cli/commands/audit.js +134 -0
- package/dist/cli/commands/audit.js.map +1 -0
- package/dist/cli/commands/blindspot.d.ts +3 -0
- package/dist/cli/commands/blindspot.d.ts.map +1 -0
- package/dist/cli/commands/blindspot.js +58 -0
- package/dist/cli/commands/blindspot.js.map +1 -0
- package/dist/cli/commands/challenge.d.ts +3 -0
- package/dist/cli/commands/challenge.d.ts.map +1 -0
- package/dist/cli/commands/challenge.js +102 -0
- package/dist/cli/commands/challenge.js.map +1 -0
- package/dist/cli/commands/feedback.d.ts +3 -0
- package/dist/cli/commands/feedback.d.ts.map +1 -0
- package/dist/cli/commands/feedback.js +72 -0
- package/dist/cli/commands/feedback.js.map +1 -0
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +22 -0
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/ledger.d.ts +3 -0
- package/dist/cli/commands/ledger.d.ts.map +1 -0
- package/dist/cli/commands/ledger.js +71 -0
- package/dist/cli/commands/ledger.js.map +1 -0
- package/dist/cli/commands/manifest.d.ts +3 -0
- package/dist/cli/commands/manifest.d.ts.map +1 -0
- package/dist/cli/commands/manifest.js +101 -0
- package/dist/cli/commands/manifest.js.map +1 -0
- package/dist/cli/commands/script-check.d.ts +3 -0
- package/dist/cli/commands/script-check.d.ts.map +1 -0
- package/dist/cli/commands/script-check.js +97 -0
- package/dist/cli/commands/script-check.js.map +1 -0
- package/dist/cli/commands/trace.d.ts +3 -0
- package/dist/cli/commands/trace.d.ts.map +1 -0
- package/dist/cli/commands/trace.js +110 -0
- package/dist/cli/commands/trace.js.map +1 -0
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/update.js +22 -9
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/index.js +16 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/capture-variable.hbs +1 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-assertion.hbs +7 -0
- package/dist/generators/test-generator/patterns/capture-patterns.d.ts +16 -0
- package/dist/generators/test-generator/patterns/capture-patterns.d.ts.map +1 -0
- package/dist/generators/test-generator/patterns/capture-patterns.js +54 -0
- package/dist/generators/test-generator/patterns/capture-patterns.js.map +1 -0
- package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/index.js +2 -0
- package/dist/generators/test-generator/patterns/index.js.map +1 -1
- package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
- package/dist/generators/test-generator/step-mapper.js +1 -0
- package/dist/generators/test-generator/step-mapper.js.map +1 -1
- package/dist/generators/test-generator/utils/data-resolver.d.ts +5 -0
- package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/data-resolver.js +17 -0
- package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
- package/dist/harness/audit.d.ts +24 -0
- package/dist/harness/audit.d.ts.map +1 -0
- package/dist/harness/audit.js +115 -0
- package/dist/harness/audit.js.map +1 -0
- package/dist/harness/blindspot.d.ts +15 -0
- package/dist/harness/blindspot.d.ts.map +1 -0
- package/dist/harness/blindspot.js +85 -0
- package/dist/harness/blindspot.js.map +1 -0
- package/dist/harness/catalog/universal-viewpoints.yaml +114 -0
- package/dist/harness/challenge.d.ts +21 -0
- package/dist/harness/challenge.d.ts.map +1 -0
- package/dist/harness/challenge.js +151 -0
- package/dist/harness/challenge.js.map +1 -0
- package/dist/harness/feedback.d.ts +29 -0
- package/dist/harness/feedback.d.ts.map +1 -0
- package/dist/harness/feedback.js +106 -0
- package/dist/harness/feedback.js.map +1 -0
- package/dist/harness/intent.d.ts +11 -0
- package/dist/harness/intent.d.ts.map +1 -0
- package/dist/harness/intent.js +86 -0
- package/dist/harness/intent.js.map +1 -0
- package/dist/harness/ledger.d.ts +42 -0
- package/dist/harness/ledger.d.ts.map +1 -0
- package/dist/harness/ledger.js +171 -0
- package/dist/harness/ledger.js.map +1 -0
- package/dist/harness/manifest.d.ts +42 -0
- package/dist/harness/manifest.d.ts.map +1 -0
- package/dist/harness/manifest.js +209 -0
- package/dist/harness/manifest.js.map +1 -0
- package/dist/harness/parse.d.ts +22 -0
- package/dist/harness/parse.d.ts.map +1 -0
- package/dist/harness/parse.js +163 -0
- package/dist/harness/parse.js.map +1 -0
- package/dist/harness/script-check.d.ts +16 -0
- package/dist/harness/script-check.d.ts.map +1 -0
- package/dist/harness/script-check.js +169 -0
- package/dist/harness/script-check.js.map +1 -0
- package/dist/harness/secret-scan.d.ts +8 -0
- package/dist/harness/secret-scan.d.ts.map +1 -0
- package/dist/harness/secret-scan.js +88 -0
- package/dist/harness/secret-scan.js.map +1 -0
- package/dist/harness/sensors.d.ts +88 -0
- package/dist/harness/sensors.d.ts.map +1 -0
- package/dist/harness/sensors.js +232 -0
- package/dist/harness/sensors.js.map +1 -0
- package/dist/harness/trace.d.ts +31 -0
- package/dist/harness/trace.d.ts.map +1 -0
- package/dist/harness/trace.js +173 -0
- package/dist/harness/trace.js.map +1 -0
- package/dist/orchestrator/ai-rules-updater.d.ts +1 -0
- package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
- package/dist/orchestrator/ai-rules-updater.js +55 -11
- package/dist/orchestrator/ai-rules-updater.js.map +1 -1
- package/dist/orchestrator/figma/spec-figma-renderer.d.ts +2 -2
- package/dist/orchestrator/figma/spec-figma-renderer.js +2 -2
- package/dist/orchestrator/figma/spec-figma-section-renderers.d.ts +1 -1
- package/dist/orchestrator/figma/spec-figma-section-renderers.js +1 -1
- package/dist/orchestrator/project-initializer.d.ts +5 -0
- package/dist/orchestrator/project-initializer.d.ts.map +1 -1
- package/dist/orchestrator/project-initializer.js +26 -6
- package/dist/orchestrator/project-initializer.js.map +1 -1
- package/dist/orchestrator/templates/ai-instructions/claude-agent-challenge.md +46 -0
- package/dist/orchestrator/templates/ai-instructions/claude-agent-discovery.md +32 -0
- package/dist/orchestrator/templates/ai-instructions/claude-agent-reviewer.md +37 -0
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-flow.md +3 -3
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +5 -5
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +45 -13
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-design.md +12 -0
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-feedback.md +36 -0
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-review.md +27 -30
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +8 -3
- package/dist/orchestrator/templates/ai-instructions/claude-config.md +1 -4
- package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-mode-figma-mcp.md +82 -0
- package/dist/orchestrator/templates/ai-instructions/{github-skill-sungen-figma-source.md → claude-skill-capture-mode-figma-pat.md} +14 -48
- package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-mode-live.md +60 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-mode-local.md +38 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-capture.md +35 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-harness-audit.md +84 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +57 -11
- package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +41 -31
- package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +53 -1
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-flow.md +3 -3
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +4 -4
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +27 -11
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-design.md +13 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-feedback.md +24 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-review.md +20 -30
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +6 -3
- package/dist/orchestrator/templates/ai-instructions/copilot-config.md +1 -4
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-figma-mcp.md +82 -0
- package/{src/orchestrator/templates/ai-instructions/claude-skill-figma-source.md → dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-figma-pat.md} +14 -48
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-live.md +60 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-local.md +38 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture.md +35 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-harness-audit.md +84 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +62 -16
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +41 -31
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +40 -1
- package/dist/orchestrator/templates/qa-context.md +90 -0
- package/dist/orchestrator/templates/readme.md +16 -13
- package/dist/orchestrator/templates/specs-test-data.ts +9 -0
- package/dist/tools/figma/figma-auth.d.ts +5 -2
- package/dist/tools/figma/figma-auth.d.ts.map +1 -1
- package/dist/tools/figma/figma-auth.js +19 -9
- package/dist/tools/figma/figma-auth.js.map +1 -1
- package/docs/orchestration-spec.md +267 -0
- package/package.json +10 -6
- package/src/cli/commands/add.ts +3 -3
- package/src/cli/commands/audit.ts +92 -0
- package/src/cli/commands/blindspot.ts +48 -0
- package/src/cli/commands/challenge.ts +55 -0
- package/src/cli/commands/feedback.ts +65 -0
- package/src/cli/commands/generate.ts +19 -0
- package/src/cli/commands/ledger.ts +61 -0
- package/src/cli/commands/manifest.ts +55 -0
- package/src/cli/commands/script-check.ts +50 -0
- package/src/cli/commands/trace.ts +60 -0
- package/src/cli/commands/update.ts +30 -10
- package/src/cli/index.ts +16 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/capture-variable.hbs +1 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-assertion.hbs +7 -0
- package/src/generators/test-generator/patterns/capture-patterns.ts +59 -0
- package/src/generators/test-generator/patterns/index.ts +2 -0
- package/src/generators/test-generator/step-mapper.ts +1 -0
- package/src/generators/test-generator/utils/data-resolver.ts +20 -0
- package/src/harness/audit.ts +112 -0
- package/src/harness/blindspot.ts +51 -0
- package/src/harness/catalog/universal-viewpoints.yaml +114 -0
- package/src/harness/challenge.ts +131 -0
- package/src/harness/feedback.ts +84 -0
- package/src/harness/intent.ts +58 -0
- package/src/harness/ledger.ts +155 -0
- package/src/harness/manifest.ts +173 -0
- package/src/harness/parse.ts +145 -0
- package/src/harness/script-check.ts +149 -0
- package/src/harness/secret-scan.ts +51 -0
- package/src/harness/sensors.ts +279 -0
- package/src/harness/trace.ts +138 -0
- package/src/orchestrator/ai-rules-updater.ts +57 -10
- package/src/orchestrator/figma/spec-figma-renderer.ts +2 -2
- package/src/orchestrator/figma/spec-figma-section-renderers.ts +1 -1
- package/src/orchestrator/project-initializer.ts +30 -7
- package/src/orchestrator/templates/ai-instructions/claude-agent-challenge.md +46 -0
- package/src/orchestrator/templates/ai-instructions/claude-agent-discovery.md +32 -0
- package/src/orchestrator/templates/ai-instructions/claude-agent-reviewer.md +37 -0
- package/src/orchestrator/templates/ai-instructions/claude-cmd-add-flow.md +3 -3
- package/src/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +5 -5
- package/src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +45 -13
- package/src/orchestrator/templates/ai-instructions/claude-cmd-design.md +12 -0
- package/src/orchestrator/templates/ai-instructions/claude-cmd-feedback.md +36 -0
- package/src/orchestrator/templates/ai-instructions/claude-cmd-review.md +27 -30
- package/src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +8 -3
- package/src/orchestrator/templates/ai-instructions/claude-config.md +1 -4
- package/src/orchestrator/templates/ai-instructions/claude-skill-capture-mode-figma-mcp.md +82 -0
- package/{dist/orchestrator/templates/ai-instructions/copilot-skill-figma-source.md → src/orchestrator/templates/ai-instructions/claude-skill-capture-mode-figma-pat.md} +14 -48
- package/src/orchestrator/templates/ai-instructions/claude-skill-capture-mode-live.md +60 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-capture-mode-local.md +38 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-capture.md +35 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-harness-audit.md +84 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +57 -11
- package/src/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +41 -31
- package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +53 -1
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-flow.md +3 -3
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +4 -4
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +27 -11
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-design.md +13 -0
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-feedback.md +24 -0
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-review.md +20 -30
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +6 -3
- package/src/orchestrator/templates/ai-instructions/copilot-config.md +1 -4
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-figma-mcp.md +82 -0
- package/{dist/orchestrator/templates/ai-instructions/claude-skill-figma-source.md → src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-figma-pat.md} +14 -48
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-live.md +60 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-local.md +38 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture.md +35 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-harness-audit.md +84 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +62 -16
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +41 -31
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +40 -1
- package/src/orchestrator/templates/qa-context.md +90 -0
- package/src/orchestrator/templates/readme.md +16 -13
- package/src/orchestrator/templates/specs-test-data.ts +9 -0
- package/src/tools/figma/figma-auth.ts +20 -9
- package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-figma.md +0 -142
- package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +0 -112
- package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-local.md +0 -73
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-figma.md +0 -142
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +0 -112
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-local.md +0 -73
- package/src/orchestrator/templates/ai-instructions/claude-skill-capture-figma.md +0 -142
- package/src/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +0 -112
- package/src/orchestrator/templates/ai-instructions/claude-skill-capture-local.md +0 -73
- package/src/orchestrator/templates/ai-instructions/copilot-skill-figma-source.md +0 -151
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-figma.md +0 -142
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +0 -112
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-local.md +0 -73
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-figma-source.md +0 -151
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Harness Sensors — deterministic quality measurement over test-design artifacts.
|
|
3
|
+
* Each sensor returns a structured finding the Orchestrator/Repair loop can act on.
|
|
4
|
+
*
|
|
5
|
+
* NOTE (honesty): the Viewpoint Gate and Duplicate sensors involve semantics, so
|
|
6
|
+
* they are HEURISTIC (keyword / skeleton based), not provably exhaustive. They use
|
|
7
|
+
* a curated seed catalog as the matching reference. See docs/orchestration-spec.md §5.2.
|
|
8
|
+
*/
|
|
9
|
+
import * as fs from 'fs';
|
|
10
|
+
import * as path from 'path';
|
|
11
|
+
import { parse as parseYaml } from 'yaml';
|
|
12
|
+
import { ScenarioInfo, ViewpointEntry } from './parse';
|
|
13
|
+
|
|
14
|
+
// Business-critical category codes (project VP-<CAT> prefixes). Configurable later.
|
|
15
|
+
const BUSINESS_CRITICAL_CATS = ['LIST', 'CART', 'PRODUCT', 'FILTER', 'CHECKOUT', 'ORDER'];
|
|
16
|
+
|
|
17
|
+
// Buckets for coverage-balance.
|
|
18
|
+
const BUCKETS: Record<string, string[]> = {
|
|
19
|
+
'business-core': BUSINESS_CRITICAL_CATS,
|
|
20
|
+
'presentation': ['UI'],
|
|
21
|
+
'validation-security': ['VAL', 'SEC', 'SUB'],
|
|
22
|
+
'behavior': ['LOGIC'],
|
|
23
|
+
'navigation': ['NAV'],
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export interface ThemeDepth {
|
|
27
|
+
requires: string; // 'data-assertion' → scenarios on this theme must assert DATA
|
|
28
|
+
cross_screen?: boolean; // genuine depth needs another screen → flow / @manual-deferred
|
|
29
|
+
keywords?: string[]; // precise data-noun keywords for depth matching
|
|
30
|
+
template?: string; // the deep step the generator should emit by default
|
|
31
|
+
}
|
|
32
|
+
export interface CatalogTheme { theme: string; keywords: string[]; depth?: ThemeDepth }
|
|
33
|
+
export interface Catalog {
|
|
34
|
+
page_types: Record<string, { detect_keywords: string[]; must_cover: CatalogTheme[] }>;
|
|
35
|
+
universal: CatalogTheme[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function loadCatalog(): Catalog {
|
|
39
|
+
const p = path.join(__dirname, 'catalog', 'universal-viewpoints.yaml');
|
|
40
|
+
return parseYaml(fs.readFileSync(p, 'utf-8')) as Catalog;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const has = (haystacks: string[], kw: string) => {
|
|
44
|
+
const k = kw.toLowerCase();
|
|
45
|
+
return haystacks.some((h) => h.includes(k));
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// ---------- Sensor 1: Viewpoint Gate (catalog-driven) ----------
|
|
49
|
+
|
|
50
|
+
export interface GateResult {
|
|
51
|
+
pageType: string | null;
|
|
52
|
+
themesTotal: number;
|
|
53
|
+
themesCovered: number; // deeply covered (has a data assertion)
|
|
54
|
+
coverageRatio: number;
|
|
55
|
+
gaps: { theme: string; keywords: string[]; status: 'missing' | 'shallow' }[];
|
|
56
|
+
universalGaps: string[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function viewpointGate(scenarios: ScenarioInfo[], viewpoints: ViewpointEntry[], catalog: Catalog): GateResult {
|
|
60
|
+
const haystacks = [
|
|
61
|
+
...scenarios.map((s) => s.haystack),
|
|
62
|
+
...viewpoints.map((v) => `${v.id} ${v.reason}`.toLowerCase()),
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
// Detect page-type by detect_keywords hit count
|
|
66
|
+
let pageType: string | null = null;
|
|
67
|
+
let best = 0;
|
|
68
|
+
for (const [pt, def] of Object.entries(catalog.page_types)) {
|
|
69
|
+
const hits = def.detect_keywords.filter((k) => has(haystacks, k)).length;
|
|
70
|
+
if (hits > best) { best = hits; pageType = pt; }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const gaps: GateResult['gaps'] = [];
|
|
74
|
+
let total = 0, covered = 0;
|
|
75
|
+
if (pageType) {
|
|
76
|
+
for (const t of catalog.page_types[pageType].must_cover) {
|
|
77
|
+
total++;
|
|
78
|
+
// Scenarios that cover this theme by keyword (include @manual — a manual
|
|
79
|
+
// scenario with a real assertion still covers the viewpoint for design).
|
|
80
|
+
const coverers = scenarios.filter((s) => t.keywords.some((k) => s.haystack.includes(k.toLowerCase())));
|
|
81
|
+
const deep = coverers.some((s) => s.hasDataAssertion);
|
|
82
|
+
if (deep) covered++;
|
|
83
|
+
else if (coverers.length > 0) gaps.push({ theme: t.theme, keywords: t.keywords, status: 'shallow' });
|
|
84
|
+
else gaps.push({ theme: t.theme, keywords: t.keywords, status: 'missing' });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const universalGaps = catalog.universal
|
|
89
|
+
.filter((t) => !t.keywords.some((k) => has(scenarios.map((s) => s.haystack), k)))
|
|
90
|
+
.map((t) => t.theme);
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
pageType,
|
|
94
|
+
themesTotal: total,
|
|
95
|
+
themesCovered: covered,
|
|
96
|
+
coverageRatio: total ? covered / total : 1,
|
|
97
|
+
gaps,
|
|
98
|
+
universalGaps,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ---------- Sensor 2: Assertion depth ----------
|
|
103
|
+
|
|
104
|
+
export type DepthVerdict = 'pass' | 'warn' | 'fail';
|
|
105
|
+
|
|
106
|
+
export interface DepthResult {
|
|
107
|
+
total: number;
|
|
108
|
+
shallowTotal: number;
|
|
109
|
+
shallowRatio: number;
|
|
110
|
+
businessCriticalTotal: number; // = depth-required scenarios (catalog data-themes)
|
|
111
|
+
businessCriticalShallow: number; // = depth-required scenarios that are shallow
|
|
112
|
+
bcDepthRatio: number; // fraction of depth-required scenarios with a real data assertion
|
|
113
|
+
shallowBusinessCritical: { name: string; category?: string }[];
|
|
114
|
+
// Depth-as-Gate (harness-roadmap P1)
|
|
115
|
+
focus: string; // intent focus driving the threshold
|
|
116
|
+
threshold: number; // required bcDepthRatio for this focus
|
|
117
|
+
verdict: DepthVerdict; // pass | warn | fail
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Intent → required depth ratio + whether a miss WARNs (smoke) or FAILs the gate.
|
|
121
|
+
// P3 will read `focus` from qa/context.md; P1 uses 'functional' by default.
|
|
122
|
+
const DEPTH_THRESHOLDS: Record<string, number> = {
|
|
123
|
+
functional: 0.7, 'e-commerce': 0.7, security: 0.8, smoke: 0.4,
|
|
124
|
+
};
|
|
125
|
+
const WARN_ONLY_FOCUS = new Set(['smoke']);
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Depth = do DATA-correctness scenarios actually assert DATA (not just visibility)?
|
|
129
|
+
* "Depth-required" is CATALOG-DRIVEN: only scenarios matching a theme whose
|
|
130
|
+
* `depth.requires === 'data-assertion'` are measured. Navigation viewpoints
|
|
131
|
+
* (category NAV) are excluded — landing on a page IS their correct assertion.
|
|
132
|
+
* This avoids the old keyword-based false-positives (e.g. "API list page").
|
|
133
|
+
*/
|
|
134
|
+
export function assertionDepth(
|
|
135
|
+
scenarios: ScenarioInfo[],
|
|
136
|
+
dataThemes: CatalogTheme[] = [],
|
|
137
|
+
focus = 'functional',
|
|
138
|
+
): DepthResult {
|
|
139
|
+
const nonManual = scenarios.filter((s) => !s.manual);
|
|
140
|
+
const shallow = nonManual.filter((s) => s.shallow);
|
|
141
|
+
|
|
142
|
+
// Precise depth keywords come from each data-theme's depth.keywords (fallback: theme keywords).
|
|
143
|
+
const depthKeywords = dataThemes.flatMap((t) => (t.depth?.keywords?.length ? t.depth.keywords : t.keywords));
|
|
144
|
+
// Dismiss/close behaviors ("Continue Shopping closes the modal", "Escape dismisses the
|
|
145
|
+
// dialog") assert a HIDDEN state — that IS the correct, data-light assertion for the
|
|
146
|
+
// behavior. They only match a data keyword incidentally (e.g. "added"), so exclude them.
|
|
147
|
+
const isDismissBehavior = (s: ScenarioInfo) =>
|
|
148
|
+
/\bis hidden\b|\bare hidden\b|\bdismiss|\bdisappear|\bno longer (visible|shown|present|displayed)\b|\bcloses? the (modal|dialog|popup|overlay|panel|menu)\b/.test(s.haystack);
|
|
149
|
+
const isDepthRequired = (s: ScenarioInfo) =>
|
|
150
|
+
s.category !== 'NAV' && !isDismissBehavior(s) && depthKeywords.some((k) => s.haystack.includes(k.toLowerCase()));
|
|
151
|
+
|
|
152
|
+
const required = nonManual.filter(isDepthRequired);
|
|
153
|
+
const reqShallow = required.filter((s) => s.shallow);
|
|
154
|
+
// No data-theme scenarios on this screen → depth is not the binding constraint
|
|
155
|
+
// (the viewpoint gate already flags missing data themes). Don't double-penalize.
|
|
156
|
+
const ratio = required.length ? 1 - reqShallow.length / required.length : 1;
|
|
157
|
+
|
|
158
|
+
const threshold = DEPTH_THRESHOLDS[focus] ?? DEPTH_THRESHOLDS.functional;
|
|
159
|
+
let verdict: DepthVerdict = 'pass';
|
|
160
|
+
if (ratio < threshold) verdict = WARN_ONLY_FOCUS.has(focus) ? 'warn' : 'fail';
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
total: nonManual.length,
|
|
164
|
+
shallowTotal: shallow.length,
|
|
165
|
+
shallowRatio: nonManual.length ? shallow.length / nonManual.length : 0,
|
|
166
|
+
businessCriticalTotal: required.length,
|
|
167
|
+
businessCriticalShallow: reqShallow.length,
|
|
168
|
+
bcDepthRatio: ratio,
|
|
169
|
+
shallowBusinessCritical: reqShallow.map((s) => ({ name: s.name, category: s.category })),
|
|
170
|
+
focus,
|
|
171
|
+
threshold,
|
|
172
|
+
verdict,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Collect data-correctness themes (depth.requires) for a page-type + universal. */
|
|
177
|
+
export function dataThemesFor(catalog: Catalog, pageType: string | null): CatalogTheme[] {
|
|
178
|
+
const themes: CatalogTheme[] = [];
|
|
179
|
+
if (pageType && catalog.page_types[pageType]) themes.push(...catalog.page_types[pageType].must_cover);
|
|
180
|
+
themes.push(...catalog.universal);
|
|
181
|
+
return themes.filter((t) => t.depth?.requires === 'data-assertion');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ---------- Sensor 3: Coverage balance ----------
|
|
185
|
+
|
|
186
|
+
export interface BalanceResult {
|
|
187
|
+
byBucket: Record<string, number>;
|
|
188
|
+
byCategory: Record<string, number>;
|
|
189
|
+
coreCount: number;
|
|
190
|
+
secondaryCount: number;
|
|
191
|
+
imbalanced: boolean;
|
|
192
|
+
note: string;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function coverageBalance(scenarios: ScenarioInfo[]): BalanceResult {
|
|
196
|
+
const byCategory: Record<string, number> = {};
|
|
197
|
+
const byBucket: Record<string, number> = {};
|
|
198
|
+
for (const b of Object.keys(BUCKETS)) byBucket[b] = 0;
|
|
199
|
+
byBucket['other'] = 0;
|
|
200
|
+
|
|
201
|
+
for (const s of scenarios) {
|
|
202
|
+
const cat = s.category || 'NONE';
|
|
203
|
+
byCategory[cat] = (byCategory[cat] || 0) + 1;
|
|
204
|
+
const bucket = Object.entries(BUCKETS).find(([, cats]) => cats.includes(cat))?.[0] || 'other';
|
|
205
|
+
byBucket[bucket]++;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const core = byBucket['business-core'];
|
|
209
|
+
const secondary = byBucket['presentation'] + byBucket['validation-security'];
|
|
210
|
+
const imbalanced = secondary > core * 1.5 && core > 0;
|
|
211
|
+
return {
|
|
212
|
+
byBucket,
|
|
213
|
+
byCategory,
|
|
214
|
+
coreCount: core,
|
|
215
|
+
secondaryCount: secondary,
|
|
216
|
+
imbalanced,
|
|
217
|
+
note: imbalanced
|
|
218
|
+
? `Secondary viewpoints (presentation+validation/security = ${secondary}) outweigh business-core (${core}) by >1.5x.`
|
|
219
|
+
: 'Balanced.',
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ---------- Sensor 4: Duplicate clusters ----------
|
|
224
|
+
|
|
225
|
+
export interface DuplicateResult {
|
|
226
|
+
clusters: { skeleton: string; scenarios: string[]; sameDataLikely: boolean }[];
|
|
227
|
+
exactDuplicateCount: number;
|
|
228
|
+
sameShapeCount: number;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function duplicateClusters(scenarios: ScenarioInfo[]): DuplicateResult {
|
|
232
|
+
const map = new Map<string, ScenarioInfo[]>();
|
|
233
|
+
for (const s of scenarios) {
|
|
234
|
+
const arr = map.get(s.stepSkeleton) || [];
|
|
235
|
+
arr.push(s);
|
|
236
|
+
map.set(s.stepSkeleton, arr);
|
|
237
|
+
}
|
|
238
|
+
const clusters = [...map.entries()]
|
|
239
|
+
.filter(([, arr]) => arr.length > 1)
|
|
240
|
+
.map(([skeleton, arr]) => ({
|
|
241
|
+
skeleton: skeleton.length > 120 ? skeleton.slice(0, 117) + '...' : skeleton,
|
|
242
|
+
scenarios: arr.map((s) => s.name),
|
|
243
|
+
// Same skeleton with data placeholders → likely an EP/data family (intentional), not a true dup.
|
|
244
|
+
sameDataLikely: !skeleton.includes('{}'),
|
|
245
|
+
}));
|
|
246
|
+
return {
|
|
247
|
+
clusters,
|
|
248
|
+
exactDuplicateCount: clusters.filter((c) => c.sameDataLikely).reduce((n, c) => n + (c.scenarios.length - 1), 0),
|
|
249
|
+
sameShapeCount: clusters.reduce((n, c) => n + c.scenarios.length, 0),
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ---------- Sensor 5: Traceability ----------
|
|
254
|
+
|
|
255
|
+
export interface TraceResult {
|
|
256
|
+
total: number;
|
|
257
|
+
withVpCode: number;
|
|
258
|
+
mappedToOverview: number;
|
|
259
|
+
withVpCodeRatio: number;
|
|
260
|
+
mappedRatio: number;
|
|
261
|
+
note: string;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function traceability(scenarios: ScenarioInfo[], viewpoints: ViewpointEntry[]): TraceResult {
|
|
265
|
+
const overviewIds = new Set(viewpoints.map((v) => v.id.toUpperCase()));
|
|
266
|
+
const withCode = scenarios.filter((s) => s.vpCode);
|
|
267
|
+
// A scenario maps to overview if its full VP code OR its category-derived id exists in overview.
|
|
268
|
+
const mapped = withCode.filter((s) => overviewIds.has(s.vpCode!) || [...overviewIds].some((id) => id.includes(s.category || '###')));
|
|
269
|
+
return {
|
|
270
|
+
total: scenarios.length,
|
|
271
|
+
withVpCode: withCode.length,
|
|
272
|
+
mappedToOverview: mapped.length,
|
|
273
|
+
withVpCodeRatio: scenarios.length ? withCode.length / scenarios.length : 0,
|
|
274
|
+
mappedRatio: scenarios.length ? mapped.length / scenarios.length : 0,
|
|
275
|
+
note: mapped.length < withCode.length * 0.5
|
|
276
|
+
? 'Scenarios use ad-hoc VP-<CAT>-NNN codes not linked to viewpoint-overview ids (weak traceability — see review Gate 4).'
|
|
277
|
+
: 'Traceable.',
|
|
278
|
+
};
|
|
279
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trace — visualise the whole executed test-design process and tell the end-user
|
|
3
|
+
* where to focus human-in-the-loop review.
|
|
4
|
+
*
|
|
5
|
+
* Aggregates the artifacts the harness already produces:
|
|
6
|
+
* - .sungen/ledger/<name>.jsonl → what ran, in what order, how many repair loops, time
|
|
7
|
+
* - .sungen/reports/<name>-audit.json → gate result, sub-scores, weak sensor (bottleneck)
|
|
8
|
+
* - .sungen/reports/<name>-script-check.json → Gherkin↔script drift
|
|
9
|
+
* - features/<name>.feature → @manual scenarios (where humans MUST verify)
|
|
10
|
+
*
|
|
11
|
+
* Outputs: a text process map + a Mermaid flowchart (paste into any viewer) +
|
|
12
|
+
* bottleneck analysis + a ranked "human-loop focus" list.
|
|
13
|
+
*/
|
|
14
|
+
import * as fs from 'fs';
|
|
15
|
+
import * as path from 'path';
|
|
16
|
+
import { segmentRuns, latestRunEvents, LedgerEvent } from './ledger';
|
|
17
|
+
|
|
18
|
+
interface ManualItem { scenario: string; reason: string }
|
|
19
|
+
|
|
20
|
+
function readJson(p: string): any | null {
|
|
21
|
+
try { return fs.existsSync(p) ? JSON.parse(fs.readFileSync(p, 'utf-8')) : null; } catch { return null; }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function readLedger(screen: string): any[] {
|
|
25
|
+
const p = path.join(process.cwd(), '.sungen', 'ledger', `${screen}.jsonl`);
|
|
26
|
+
if (!fs.existsSync(p)) return [];
|
|
27
|
+
return fs.readFileSync(p, 'utf-8').split('\n').filter(Boolean).map((l) => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Parse @manual scenarios + the explanatory comment line above each. */
|
|
31
|
+
function parseManual(featurePath: string): ManualItem[] {
|
|
32
|
+
if (!fs.existsSync(featurePath)) return [];
|
|
33
|
+
const lines = fs.readFileSync(featurePath, 'utf-8').split('\n');
|
|
34
|
+
const out: ManualItem[] = [];
|
|
35
|
+
for (let i = 0; i < lines.length; i++) {
|
|
36
|
+
const m = lines[i].match(/^\s*Scenario:\s*(.+)$/);
|
|
37
|
+
if (!m) continue;
|
|
38
|
+
// look back for tag line + a reason comment
|
|
39
|
+
let manual = false; let reason = '';
|
|
40
|
+
for (let j = i - 1; j >= 0 && j >= i - 4; j--) {
|
|
41
|
+
const l = lines[j].trim();
|
|
42
|
+
if (/^@/.test(l) && /@manual/.test(l)) manual = true;
|
|
43
|
+
else if (/^#/.test(l) && !reason) reason = l.replace(/^#+\s*/, '');
|
|
44
|
+
else if (l === '' || /^Scenario:/.test(l)) break;
|
|
45
|
+
}
|
|
46
|
+
if (manual) out.push({ scenario: m[1].trim(), reason: reason || '(no reason noted)' });
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface TraceReport {
|
|
52
|
+
screen: string;
|
|
53
|
+
runs: number; // total runs on file (process map shows the latest)
|
|
54
|
+
ledger: { step: string; ms: number }[];
|
|
55
|
+
repairRounds: number;
|
|
56
|
+
totalMs: number;
|
|
57
|
+
recordedSteps: string[];
|
|
58
|
+
missingSteps: string[]; // expected phases not recorded in ledger
|
|
59
|
+
audit: { score?: number; gate?: string; weakest?: string; findings?: number; gaps?: string[] } | null;
|
|
60
|
+
drift: string | null;
|
|
61
|
+
manual: ManualItem[];
|
|
62
|
+
bottlenecks: string[];
|
|
63
|
+
humanFocus: string[];
|
|
64
|
+
mermaid: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const EXPECTED_PHASES = ['discovery', 'viewpoint', 'gherkin', 'audit', 'repair'];
|
|
68
|
+
|
|
69
|
+
export function buildTrace(screenDir: string, screenName: string): TraceReport {
|
|
70
|
+
const allEvents = readLedger(screenName) as LedgerEvent[];
|
|
71
|
+
const runs = segmentRuns(allEvents).length;
|
|
72
|
+
const events = latestRunEvents(allEvents); // scope to the most recent run (P2)
|
|
73
|
+
const ledger = events.map((e) => ({ step: e.step as string, ms: (e.ms as number) || 0 }));
|
|
74
|
+
const repairRounds = new Set(events.filter((e) => /^repair/i.test(e.step)).map((e) => e.step)).size;
|
|
75
|
+
const totalMs = ledger.reduce((n, e) => n + e.ms, 0);
|
|
76
|
+
const recordedSteps = [...new Set(ledger.map((e) => e.step.replace(/:\d+$/, '')))];
|
|
77
|
+
const missingSteps = EXPECTED_PHASES.filter((p) => !recordedSteps.includes(p));
|
|
78
|
+
|
|
79
|
+
const auditRaw = readJson(path.join(process.cwd(), '.sungen', 'reports', `${screenName}-audit.json`));
|
|
80
|
+
let audit: TraceReport['audit'] = null;
|
|
81
|
+
if (auditRaw) {
|
|
82
|
+
const subs: Record<string, number> = {
|
|
83
|
+
coverage: auditRaw.score?.coverage, businessDepth: auditRaw.score?.businessDepth,
|
|
84
|
+
balance: auditRaw.score?.balance, traceability: auditRaw.score?.traceability,
|
|
85
|
+
};
|
|
86
|
+
const weakest = Object.entries(subs).filter(([, v]) => typeof v === 'number').sort((a, b) => a[1] - b[1])[0]?.[0];
|
|
87
|
+
audit = {
|
|
88
|
+
score: auditRaw.score?.overall, gate: auditRaw.gateStatus, weakest,
|
|
89
|
+
findings: (auditRaw.findings || []).length,
|
|
90
|
+
gaps: (auditRaw.gate?.gaps || []).map((g: any) => `${g.theme} (${g.status})`),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const scRaw = readJson(path.join(process.cwd(), '.sungen', 'reports', `${screenName}-script-check.json`));
|
|
95
|
+
const drift = scRaw ? scRaw.drift : null;
|
|
96
|
+
|
|
97
|
+
const manual = parseManual(path.join(screenDir, 'features', `${screenName}.feature`));
|
|
98
|
+
|
|
99
|
+
// Bottlenecks
|
|
100
|
+
const bottlenecks: string[] = [];
|
|
101
|
+
if (repairRounds >= 3) bottlenecks.push(`Repair loop hit ${repairRounds} rounds (near/over budget) → first-pass generation (Guide) is the bottleneck.`);
|
|
102
|
+
if (audit?.weakest) bottlenecks.push(`Lowest audit dimension: ${audit.weakest} → focus engine improvement there.`);
|
|
103
|
+
if (audit?.gaps?.length) bottlenecks.push(`Gate gaps: ${audit.gaps.join(', ')}.`);
|
|
104
|
+
if (drift === 'drift') bottlenecks.push('Spec DRIFT vs Gherkin → regenerate (never hand-edit specs).');
|
|
105
|
+
if (missingSteps.length) bottlenecks.push(`Ledger missing phases: ${missingSteps.join(', ')} → instrument \`sungen ledger record\` at each phase for full observability.`);
|
|
106
|
+
if (bottlenecks.length === 0) bottlenecks.push('No bottleneck detected from available signals.');
|
|
107
|
+
|
|
108
|
+
// Human-loop focus (where the user must look)
|
|
109
|
+
const humanFocus: string[] = [];
|
|
110
|
+
if (manual.length) humanFocus.push(`${manual.length} @manual scenario(s) need HUMAN verification (not automated):`);
|
|
111
|
+
for (const m of manual.slice(0, 12)) humanFocus.push(` • ${m.scenario} — ${m.reason}`);
|
|
112
|
+
if (audit?.gaps?.length) humanFocus.push(`Critical gaps deferred/uncovered: ${audit.gaps.join(', ')} — decide flow vs accept.`);
|
|
113
|
+
if (humanFocus.length === 0) humanFocus.push('No manual/uncovered items — automation covers the critical viewpoints.');
|
|
114
|
+
|
|
115
|
+
// Mermaid flowchart of the executed process
|
|
116
|
+
const phaseSeq = ledger.length
|
|
117
|
+
? ledger.map((e) => e.step)
|
|
118
|
+
: ['(no ledger — process not instrumented)'];
|
|
119
|
+
const nodes = phaseSeq.map((s, i) => ` S${i}["${s}${ledger[i] ? ` (${ledger[i].ms}ms)` : ''}"]`).join('\n');
|
|
120
|
+
const edges = phaseSeq.map((_, i) => (i < phaseSeq.length - 1 ? ` S${i} --> S${i + 1}` : '')).filter(Boolean).join('\n');
|
|
121
|
+
const mermaid = [
|
|
122
|
+
'flowchart TD',
|
|
123
|
+
' IN["spec / figma / ui / live"] --> DISC[discovery]',
|
|
124
|
+
' DISC --> VP[viewpoint overview]',
|
|
125
|
+
' VP --> GEN[generate Gherkin]',
|
|
126
|
+
' GEN --> AUD{sungen audit gate}',
|
|
127
|
+
repairRounds ? ` AUD -- FAIL --> REP["repair ×${repairRounds}"]` : '',
|
|
128
|
+
repairRounds ? ' REP --> AUD' : '',
|
|
129
|
+
` AUD -- ${audit?.gate === 'PASS' ? 'PASS' : 'still failing'} --> DONE[converge]`,
|
|
130
|
+
' DONE --> SC{script-check 1:1}',
|
|
131
|
+
` SC -- ${drift === 'drift' ? 'DRIFT → regenerate' : 'in-sync'} --> OUT[generate → run-test → delivery]`,
|
|
132
|
+
].filter(Boolean).join('\n');
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
screen: screenName, runs, ledger, repairRounds, totalMs, recordedSteps, missingSteps,
|
|
136
|
+
audit, drift, manual, bottlenecks, humanFocus, mermaid,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
@@ -18,21 +18,25 @@ export const AI_RULES_FILE_MAPPING: [string, string][] = [
|
|
|
18
18
|
['claude-cmd-add-screen.md', '.claude/commands/sungen/add-screen.md'],
|
|
19
19
|
['claude-cmd-add-flow.md', '.claude/commands/sungen/add-flow.md'],
|
|
20
20
|
['claude-cmd-create-test.md', '.claude/commands/sungen/create-test.md'],
|
|
21
|
+
['claude-cmd-design.md', '.claude/commands/sungen/design.md'],
|
|
21
22
|
['claude-cmd-run-test.md', '.claude/commands/sungen/run-test.md'],
|
|
22
23
|
['claude-cmd-review.md', '.claude/commands/sungen/review.md'],
|
|
23
24
|
['claude-cmd-delivery.md', '.claude/commands/sungen/delivery.md'],
|
|
24
25
|
['claude-cmd-dashboard.md', '.claude/commands/sungen/dashboard.md'],
|
|
25
26
|
['claude-cmd-locale.md', '.claude/commands/sungen/locale.md'],
|
|
27
|
+
['claude-cmd-feedback.md', '.claude/commands/sungen/feedback.md'],
|
|
26
28
|
|
|
27
29
|
// Commands — GitHub Copilot
|
|
28
30
|
['copilot-cmd-add-screen.md', '.github/prompts/sungen-add-screen.prompt.md'],
|
|
29
31
|
['copilot-cmd-add-flow.md', '.github/prompts/sungen-add-flow.prompt.md'],
|
|
30
32
|
['copilot-cmd-create-test.md', '.github/prompts/sungen-create-test.prompt.md'],
|
|
33
|
+
['copilot-cmd-design.md', '.github/prompts/sungen-design.prompt.md'],
|
|
31
34
|
['copilot-cmd-run-test.md', '.github/prompts/sungen-run-test.prompt.md'],
|
|
32
35
|
['copilot-cmd-review.md', '.github/prompts/sungen-review.prompt.md'],
|
|
33
36
|
['copilot-cmd-delivery.md', '.github/prompts/sungen-delivery.prompt.md'],
|
|
34
37
|
['copilot-cmd-dashboard.md', '.github/prompts/sungen-dashboard.prompt.md'],
|
|
35
38
|
['copilot-cmd-locale.md', '.github/prompts/sungen-locale.prompt.md'],
|
|
39
|
+
['copilot-cmd-feedback.md', '.github/prompts/sungen-feedback.prompt.md'],
|
|
36
40
|
|
|
37
41
|
// Skills — Claude Code
|
|
38
42
|
['claude-skill-gherkin-syntax.md', '.claude/skills/sungen-gherkin-syntax/SKILL.md'],
|
|
@@ -42,6 +46,7 @@ export const AI_RULES_FILE_MAPPING: [string, string][] = [
|
|
|
42
46
|
['claude-skill-test-design-techniques.md', '.claude/skills/sungen-test-design-techniques/SKILL.md'],
|
|
43
47
|
['claude-skill-selector-fix.md', '.claude/skills/sungen-selector-fix/SKILL.md'],
|
|
44
48
|
['claude-skill-tc-review.md', '.claude/skills/sungen-tc-review/SKILL.md'],
|
|
49
|
+
['claude-skill-harness-audit.md', '.claude/skills/sungen-harness-audit/SKILL.md'],
|
|
45
50
|
['claude-skill-viewpoint.md', '.claude/skills/sungen-viewpoint/SKILL.md'],
|
|
46
51
|
['claude-skill-viewpoint-group-a-data-entry.md', '.claude/skills/sungen-viewpoint/group-a-data-entry.md'],
|
|
47
52
|
['claude-skill-viewpoint-group-b-data-ops.md', '.claude/skills/sungen-viewpoint/group-b-data-ops.md'],
|
|
@@ -50,14 +55,19 @@ export const AI_RULES_FILE_MAPPING: [string, string][] = [
|
|
|
50
55
|
['claude-skill-viewpoint-group-e-identity.md', '.claude/skills/sungen-viewpoint/group-e-identity.md'],
|
|
51
56
|
['claude-skill-delivery.md', '.claude/skills/sungen-delivery/SKILL.md'],
|
|
52
57
|
['claude-skill-dashboard.md', '.claude/skills/sungen-dashboard/SKILL.md'],
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
['claude-skill-capture
|
|
56
|
-
['claude-skill-figma-
|
|
58
|
+
// sungen-capture — router skill (1 SKILL.md + 4 mode files). Replaces the former
|
|
59
|
+
// capture-figma / capture-live / capture-local / figma-source standalone skills.
|
|
60
|
+
['claude-skill-capture.md', '.claude/skills/sungen-capture/SKILL.md'],
|
|
61
|
+
['claude-skill-capture-mode-figma-mcp.md', '.claude/skills/sungen-capture/mode-figma-mcp.md'],
|
|
62
|
+
['claude-skill-capture-mode-figma-pat.md', '.claude/skills/sungen-capture/mode-figma-pat.md'],
|
|
63
|
+
['claude-skill-capture-mode-live.md', '.claude/skills/sungen-capture/mode-live.md'],
|
|
64
|
+
['claude-skill-capture-mode-local.md', '.claude/skills/sungen-capture/mode-local.md'],
|
|
57
65
|
['claude-skill-locale.md', '.claude/skills/sungen-locale/SKILL.md'],
|
|
58
66
|
|
|
59
|
-
//
|
|
60
|
-
['
|
|
67
|
+
// Agents — Claude Code sub-agents (isolated context). Copilot runs these inline.
|
|
68
|
+
['claude-agent-reviewer.md', '.claude/agents/sungen-reviewer.md'],
|
|
69
|
+
['claude-agent-discovery.md', '.claude/agents/sungen-discovery.md'],
|
|
70
|
+
['claude-agent-challenge.md', '.claude/agents/sungen-challenge.md'],
|
|
61
71
|
|
|
62
72
|
// Skills — GitHub Copilot
|
|
63
73
|
['github-skill-sungen-gherkin-syntax.md', '.github/skills/sungen-gherkin-syntax/SKILL.md'],
|
|
@@ -67,6 +77,7 @@ export const AI_RULES_FILE_MAPPING: [string, string][] = [
|
|
|
67
77
|
['github-skill-sungen-test-design-techniques.md', '.github/skills/sungen-test-design-techniques/SKILL.md'],
|
|
68
78
|
['github-skill-sungen-selector-fix.md', '.github/skills/sungen-selector-fix/SKILL.md'],
|
|
69
79
|
['github-skill-sungen-tc-review.md', '.github/skills/sungen-tc-review/SKILL.md'],
|
|
80
|
+
['github-skill-sungen-harness-audit.md', '.github/skills/sungen-harness-audit/SKILL.md'],
|
|
70
81
|
['github-skill-sungen-viewpoint.md', '.github/skills/sungen-viewpoint/SKILL.md'],
|
|
71
82
|
['github-skill-sungen-viewpoint-group-a-data-entry.md', '.github/skills/sungen-viewpoint/group-a-data-entry.md'],
|
|
72
83
|
['github-skill-sungen-viewpoint-group-b-data-ops.md', '.github/skills/sungen-viewpoint/group-b-data-ops.md'],
|
|
@@ -75,13 +86,29 @@ export const AI_RULES_FILE_MAPPING: [string, string][] = [
|
|
|
75
86
|
['github-skill-sungen-viewpoint-group-e-identity.md', '.github/skills/sungen-viewpoint/group-e-identity.md'],
|
|
76
87
|
['github-skill-sungen-delivery.md', '.github/skills/sungen-delivery/SKILL.md'],
|
|
77
88
|
['github-skill-sungen-dashboard.md', '.github/skills/sungen-dashboard/SKILL.md'],
|
|
78
|
-
['github-skill-sungen-capture
|
|
79
|
-
['github-skill-sungen-capture-
|
|
80
|
-
['github-skill-sungen-capture-
|
|
81
|
-
['github-skill-sungen-
|
|
89
|
+
['github-skill-sungen-capture.md', '.github/skills/sungen-capture/SKILL.md'],
|
|
90
|
+
['github-skill-sungen-capture-mode-figma-mcp.md', '.github/skills/sungen-capture/mode-figma-mcp.md'],
|
|
91
|
+
['github-skill-sungen-capture-mode-figma-pat.md', '.github/skills/sungen-capture/mode-figma-pat.md'],
|
|
92
|
+
['github-skill-sungen-capture-mode-live.md', '.github/skills/sungen-capture/mode-live.md'],
|
|
93
|
+
['github-skill-sungen-capture-mode-local.md', '.github/skills/sungen-capture/mode-local.md'],
|
|
82
94
|
['github-skill-sungen-locale.md', '.github/skills/sungen-locale/SKILL.md'],
|
|
83
95
|
];
|
|
84
96
|
|
|
97
|
+
// Skill/asset directories retired in a previous refactor. `sungen update` removes
|
|
98
|
+
// these so already-initialized projects don't keep loading stale skill descriptions.
|
|
99
|
+
// (v3.0: the four capture/figma skills were merged into the `sungen-capture` router.)
|
|
100
|
+
export const STALE_AI_RULES_PATHS: string[] = [
|
|
101
|
+
'.claude/skills/sungen-capture-figma',
|
|
102
|
+
'.claude/skills/sungen-capture-live',
|
|
103
|
+
'.claude/skills/sungen-capture-local',
|
|
104
|
+
'.claude/skills/sungen-figma-source',
|
|
105
|
+
'.github/skills/sungen-capture-figma',
|
|
106
|
+
'.github/skills/sungen-capture-live',
|
|
107
|
+
'.github/skills/sungen-capture-local',
|
|
108
|
+
'.github/skills/sungen-figma-source',
|
|
109
|
+
'.github/prompts/sungen-figma-source.prompt.md',
|
|
110
|
+
];
|
|
111
|
+
|
|
85
112
|
export class AIRulesUpdater {
|
|
86
113
|
private cwd: string;
|
|
87
114
|
private aiTemplateDir: string;
|
|
@@ -133,11 +160,31 @@ export class AIRulesUpdater {
|
|
|
133
160
|
}
|
|
134
161
|
}
|
|
135
162
|
|
|
163
|
+
// Remove retired skills/assets so stale descriptions stop loading.
|
|
164
|
+
const removed: string[] = [];
|
|
165
|
+
for (const staleRelPath of STALE_AI_RULES_PATHS) {
|
|
166
|
+
const stalePath = path.join(this.cwd, staleRelPath);
|
|
167
|
+
if (fs.existsSync(stalePath)) {
|
|
168
|
+
if (!dryRun) {
|
|
169
|
+
fs.rmSync(stalePath, { recursive: true, force: true });
|
|
170
|
+
}
|
|
171
|
+
removed.push(staleRelPath);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
136
175
|
// Print results
|
|
137
176
|
if (dryRun) {
|
|
138
177
|
console.log('📋 Dry run — no files changed\n');
|
|
139
178
|
}
|
|
140
179
|
|
|
180
|
+
if (removed.length > 0) {
|
|
181
|
+
console.log(`🗑️ Removed retired assets (${removed.length}):`);
|
|
182
|
+
for (const f of removed) {
|
|
183
|
+
console.log(` ${f}`);
|
|
184
|
+
}
|
|
185
|
+
console.log();
|
|
186
|
+
}
|
|
187
|
+
|
|
141
188
|
if (updated.length > 0) {
|
|
142
189
|
console.log(`✏️ Updated (${updated.length}):`);
|
|
143
190
|
for (const f of updated) {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* auto-gen banner, Frame metadata, Screenshots, and a SYNTHESIS marker.
|
|
6
6
|
* Narrative sections (Purpose, ASCII Layout, Regions, Actions, Form Fields,
|
|
7
7
|
* Data Columns, Navigation) are appended BELOW the marker by the
|
|
8
|
-
* sungen-figma-
|
|
8
|
+
* sungen-capture skill (mode figma-pat), which reads the raw cached node JSON.
|
|
9
9
|
*
|
|
10
10
|
* Section renderers live in spec-figma-section-renderers.ts.
|
|
11
11
|
*
|
|
@@ -55,7 +55,7 @@ export interface RenderSpecFigmaInput {
|
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
57
|
* Render the deterministic envelope for spec_figma.md.
|
|
58
|
-
* The LLM synthesis step (sungen-figma-
|
|
58
|
+
* The LLM synthesis step (sungen-capture skill (mode figma-pat)) appends the narrative
|
|
59
59
|
* sections AFTER the SYNTHESIS_MARKER comment. Re-synthesis replaces
|
|
60
60
|
* everything from the marker to EOF.
|
|
61
61
|
*
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* needs: frontmatter, Frame metadata, Screenshots, and the SYNTHESIS marker.
|
|
6
6
|
* All prose sections (Purpose, ASCII Layout, Regions, Actions, Form Fields,
|
|
7
7
|
* Data Columns, Navigation) are appended below the marker by the
|
|
8
|
-
* sungen-figma-
|
|
8
|
+
* sungen-capture skill (mode figma-pat).
|
|
9
9
|
*
|
|
10
10
|
* Pure — no I/O.
|
|
11
11
|
*/
|
|
@@ -39,6 +39,9 @@ export class ProjectInitializer {
|
|
|
39
39
|
// Create directories
|
|
40
40
|
this.createDirectories();
|
|
41
41
|
|
|
42
|
+
// Create qa/context.md for QA lead to fill project-wide context
|
|
43
|
+
this.createContext();
|
|
44
|
+
|
|
42
45
|
// Ensure package.json and install Playwright
|
|
43
46
|
await this.setupDependencies();
|
|
44
47
|
|
|
@@ -147,10 +150,12 @@ export class ProjectInitializer {
|
|
|
147
150
|
const gitignorePath = path.join(this.cwd, '.gitignore');
|
|
148
151
|
const sungenEntries = [
|
|
149
152
|
'# Sungen generated files',
|
|
150
|
-
'specs/.auth/',
|
|
153
|
+
'specs/.auth/', // session storage state = cookies/tokens (sensitive)
|
|
151
154
|
'test-results/',
|
|
152
155
|
'playwright-report/',
|
|
153
|
-
'.playwright-mcp/'
|
|
156
|
+
'.playwright-mcp/',
|
|
157
|
+
'.sungen/figma-cache/', // raw Figma node JSON (design IP)
|
|
158
|
+
'.sungen/feedback/', // local QA feedback (may contain product-sensitive notes)
|
|
154
159
|
];
|
|
155
160
|
|
|
156
161
|
if (!fs.existsSync(gitignorePath)) {
|
|
@@ -159,12 +164,13 @@ export class ProjectInitializer {
|
|
|
159
164
|
fs.writeFileSync(gitignorePath, content, 'utf-8');
|
|
160
165
|
this.createdItems.push('.gitignore');
|
|
161
166
|
} else {
|
|
162
|
-
// Append Sungen entries
|
|
167
|
+
// Append any Sungen entries that are missing (idempotent — covers projects
|
|
168
|
+
// created before new sensitive paths were added).
|
|
163
169
|
let content = fs.readFileSync(gitignorePath, 'utf-8');
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
content += '\n' +
|
|
170
|
+
const missing = sungenEntries.filter((e) => e.startsWith('#') ? false : !content.includes(e));
|
|
171
|
+
if (missing.length) {
|
|
172
|
+
const block = content.includes('# Sungen generated files') ? missing : ['# Sungen generated files', ...missing];
|
|
173
|
+
content += '\n' + block.join('\n') + '\n';
|
|
168
174
|
fs.writeFileSync(gitignorePath, content, 'utf-8');
|
|
169
175
|
this.createdItems.push('.gitignore (updated)');
|
|
170
176
|
} else {
|
|
@@ -363,6 +369,23 @@ export class ProjectInitializer {
|
|
|
363
369
|
|
|
364
370
|
}
|
|
365
371
|
|
|
372
|
+
/**
|
|
373
|
+
* Create qa/context.md for the QA lead to fill project-wide context
|
|
374
|
+
* (roles, testing strategy, global rules, error patterns).
|
|
375
|
+
*/
|
|
376
|
+
private createContext(): void {
|
|
377
|
+
const contextPath = path.join(this.cwd, 'qa', 'context.md');
|
|
378
|
+
|
|
379
|
+
if (fs.existsSync(contextPath)) {
|
|
380
|
+
this.skippedItems.push('qa/context.md');
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const content = this.readTemplate('qa-context.md');
|
|
385
|
+
fs.writeFileSync(contextPath, content, 'utf-8');
|
|
386
|
+
this.createdItems.push('qa/context.md');
|
|
387
|
+
}
|
|
388
|
+
|
|
366
389
|
/**
|
|
367
390
|
* Create specs/base.ts for shared browser context
|
|
368
391
|
*/
|