@sun-asterisk/sungen 3.1.1 → 3.1.2-beta.101

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.
Files changed (162) hide show
  1. package/README.md +4 -428
  2. package/dist/capabilities/builtins.d.ts +31 -0
  3. package/dist/capabilities/builtins.d.ts.map +1 -0
  4. package/dist/capabilities/builtins.js +84 -0
  5. package/dist/capabilities/builtins.js.map +1 -0
  6. package/dist/capabilities/context-router.d.ts +34 -0
  7. package/dist/capabilities/context-router.d.ts.map +1 -0
  8. package/dist/capabilities/context-router.js +49 -0
  9. package/dist/capabilities/context-router.js.map +1 -0
  10. package/dist/capabilities/context.d.ts +51 -0
  11. package/dist/capabilities/context.d.ts.map +1 -0
  12. package/dist/capabilities/context.js +17 -0
  13. package/dist/capabilities/context.js.map +1 -0
  14. package/dist/capabilities/discover.d.ts +2 -0
  15. package/dist/capabilities/discover.d.ts.map +1 -0
  16. package/dist/capabilities/discover.js +48 -0
  17. package/dist/capabilities/discover.js.map +1 -0
  18. package/dist/capabilities/registry.d.ts +90 -0
  19. package/dist/capabilities/registry.d.ts.map +1 -0
  20. package/dist/capabilities/registry.js +43 -0
  21. package/dist/capabilities/registry.js.map +1 -0
  22. package/dist/capabilities/sensor.d.ts +49 -0
  23. package/dist/capabilities/sensor.d.ts.map +1 -0
  24. package/dist/capabilities/sensor.js +3 -0
  25. package/dist/capabilities/sensor.js.map +1 -0
  26. package/dist/cli/commands/generate.d.ts.map +1 -1
  27. package/dist/cli/commands/generate.js +7 -3
  28. package/dist/cli/commands/generate.js.map +1 -1
  29. package/dist/cli/index.js +10 -1
  30. package/dist/cli/index.js.map +1 -1
  31. package/dist/exporters/spec-parser.d.ts.map +1 -1
  32. package/dist/exporters/spec-parser.js +4 -1
  33. package/dist/exporters/spec-parser.js.map +1 -1
  34. package/dist/generators/test-generator/adapters/adapter-interface.d.ts +1 -0
  35. package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
  36. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +1 -0
  37. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -1
  38. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -1
  39. package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
  40. package/dist/generators/test-generator/code-generator.d.ts +18 -9
  41. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  42. package/dist/generators/test-generator/code-generator.js +76 -119
  43. package/dist/generators/test-generator/code-generator.js.map +1 -1
  44. package/dist/generators/test-generator/patterns/index.d.ts +0 -10
  45. package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
  46. package/dist/generators/test-generator/patterns/index.js +10 -47
  47. package/dist/generators/test-generator/patterns/index.js.map +1 -1
  48. package/dist/generators/test-generator/template-engine.d.ts +1 -0
  49. package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
  50. package/dist/generators/test-generator/template-engine.js +1 -1
  51. package/dist/generators/test-generator/template-engine.js.map +1 -1
  52. package/dist/harness/annotation-overrides.d.ts +9 -0
  53. package/dist/harness/annotation-overrides.d.ts.map +1 -0
  54. package/dist/harness/annotation-overrides.js +36 -0
  55. package/dist/harness/annotation-overrides.js.map +1 -0
  56. package/dist/harness/audit.d.ts.map +1 -1
  57. package/dist/harness/audit.js +35 -7
  58. package/dist/harness/audit.js.map +1 -1
  59. package/dist/harness/catalog/drivers.yaml +35 -12
  60. package/dist/harness/parse.d.ts +1 -0
  61. package/dist/harness/parse.d.ts.map +1 -1
  62. package/dist/harness/parse.js +13 -4
  63. package/dist/harness/parse.js.map +1 -1
  64. package/dist/index.d.ts +20 -0
  65. package/dist/index.d.ts.map +1 -0
  66. package/dist/index.js +32 -0
  67. package/dist/index.js.map +1 -0
  68. package/dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +4 -3
  69. package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +1 -0
  70. package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +3 -0
  71. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +4 -3
  72. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +1 -0
  73. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +3 -0
  74. package/dist/orchestrator/templates/specs-api.d.ts +19 -0
  75. package/dist/orchestrator/templates/specs-api.d.ts.map +1 -0
  76. package/dist/orchestrator/templates/specs-api.js +128 -0
  77. package/dist/orchestrator/templates/specs-api.js.map +1 -0
  78. package/dist/orchestrator/templates/specs-api.ts +101 -0
  79. package/package.json +7 -30
  80. package/src/capabilities/builtins.ts +85 -0
  81. package/src/capabilities/context-router.ts +66 -0
  82. package/src/capabilities/context.ts +46 -0
  83. package/src/capabilities/discover.ts +42 -0
  84. package/src/capabilities/registry.ts +111 -0
  85. package/src/capabilities/sensor.ts +47 -0
  86. package/src/cli/commands/generate.ts +7 -3
  87. package/src/cli/index.ts +10 -1
  88. package/src/exporters/spec-parser.ts +4 -1
  89. package/src/generators/test-generator/adapters/adapter-interface.ts +1 -1
  90. package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +1 -1
  91. package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
  92. package/src/generators/test-generator/code-generator.ts +71 -118
  93. package/src/generators/test-generator/patterns/index.ts +9 -35
  94. package/src/generators/test-generator/template-engine.ts +2 -2
  95. package/src/harness/annotation-overrides.ts +25 -0
  96. package/src/harness/audit.ts +37 -8
  97. package/src/harness/catalog/drivers.yaml +35 -12
  98. package/src/harness/parse.ts +7 -2
  99. package/src/index.ts +30 -0
  100. package/src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +4 -3
  101. package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +1 -0
  102. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +3 -0
  103. package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +4 -3
  104. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +1 -0
  105. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +3 -0
  106. package/src/orchestrator/templates/specs-api.ts +101 -0
  107. package/dist/generators/test-generator/patterns/assertion-patterns.d.ts +0 -7
  108. package/dist/generators/test-generator/patterns/assertion-patterns.d.ts.map +0 -1
  109. package/dist/generators/test-generator/patterns/assertion-patterns.js +0 -626
  110. package/dist/generators/test-generator/patterns/assertion-patterns.js.map +0 -1
  111. package/dist/generators/test-generator/patterns/capture-patterns.d.ts +0 -21
  112. package/dist/generators/test-generator/patterns/capture-patterns.d.ts.map +0 -1
  113. package/dist/generators/test-generator/patterns/capture-patterns.js +0 -87
  114. package/dist/generators/test-generator/patterns/capture-patterns.js.map +0 -1
  115. package/dist/generators/test-generator/patterns/database-patterns.d.ts +0 -6
  116. package/dist/generators/test-generator/patterns/database-patterns.d.ts.map +0 -1
  117. package/dist/generators/test-generator/patterns/database-patterns.js +0 -95
  118. package/dist/generators/test-generator/patterns/database-patterns.js.map +0 -1
  119. package/dist/generators/test-generator/patterns/form-patterns.d.ts +0 -6
  120. package/dist/generators/test-generator/patterns/form-patterns.d.ts.map +0 -1
  121. package/dist/generators/test-generator/patterns/form-patterns.js +0 -160
  122. package/dist/generators/test-generator/patterns/form-patterns.js.map +0 -1
  123. package/dist/generators/test-generator/patterns/interaction-patterns.d.ts +0 -6
  124. package/dist/generators/test-generator/patterns/interaction-patterns.d.ts.map +0 -1
  125. package/dist/generators/test-generator/patterns/interaction-patterns.js +0 -433
  126. package/dist/generators/test-generator/patterns/interaction-patterns.js.map +0 -1
  127. package/dist/generators/test-generator/patterns/keyboard-patterns.d.ts +0 -7
  128. package/dist/generators/test-generator/patterns/keyboard-patterns.d.ts.map +0 -1
  129. package/dist/generators/test-generator/patterns/keyboard-patterns.js +0 -47
  130. package/dist/generators/test-generator/patterns/keyboard-patterns.js.map +0 -1
  131. package/dist/generators/test-generator/patterns/navigation-patterns.d.ts +0 -6
  132. package/dist/generators/test-generator/patterns/navigation-patterns.d.ts.map +0 -1
  133. package/dist/generators/test-generator/patterns/navigation-patterns.js +0 -125
  134. package/dist/generators/test-generator/patterns/navigation-patterns.js.map +0 -1
  135. package/dist/generators/test-generator/patterns/scope-patterns.d.ts +0 -7
  136. package/dist/generators/test-generator/patterns/scope-patterns.d.ts.map +0 -1
  137. package/dist/generators/test-generator/patterns/scope-patterns.js +0 -36
  138. package/dist/generators/test-generator/patterns/scope-patterns.js.map +0 -1
  139. package/dist/generators/test-generator/patterns/scroll-patterns.d.ts +0 -7
  140. package/dist/generators/test-generator/patterns/scroll-patterns.d.ts.map +0 -1
  141. package/dist/generators/test-generator/patterns/scroll-patterns.js +0 -25
  142. package/dist/generators/test-generator/patterns/scroll-patterns.js.map +0 -1
  143. package/dist/generators/test-generator/patterns/setup-patterns.d.ts +0 -6
  144. package/dist/generators/test-generator/patterns/setup-patterns.d.ts.map +0 -1
  145. package/dist/generators/test-generator/patterns/setup-patterns.js +0 -72
  146. package/dist/generators/test-generator/patterns/setup-patterns.js.map +0 -1
  147. package/dist/generators/test-generator/patterns/table-patterns.d.ts +0 -19
  148. package/dist/generators/test-generator/patterns/table-patterns.d.ts.map +0 -1
  149. package/dist/generators/test-generator/patterns/table-patterns.js +0 -239
  150. package/dist/generators/test-generator/patterns/table-patterns.js.map +0 -1
  151. package/docs/orchestration-spec.md +0 -267
  152. package/src/generators/test-generator/patterns/assertion-patterns.ts +0 -691
  153. package/src/generators/test-generator/patterns/capture-patterns.ts +0 -97
  154. package/src/generators/test-generator/patterns/database-patterns.ts +0 -96
  155. package/src/generators/test-generator/patterns/form-patterns.ts +0 -167
  156. package/src/generators/test-generator/patterns/interaction-patterns.ts +0 -465
  157. package/src/generators/test-generator/patterns/keyboard-patterns.ts +0 -51
  158. package/src/generators/test-generator/patterns/navigation-patterns.ts +0 -140
  159. package/src/generators/test-generator/patterns/scope-patterns.ts +0 -40
  160. package/src/generators/test-generator/patterns/scroll-patterns.ts +0 -27
  161. package/src/generators/test-generator/patterns/setup-patterns.ts +0 -76
  162. package/src/generators/test-generator/patterns/table-patterns.ts +0 -279
@@ -11,7 +11,7 @@ import * as fs from 'fs';
11
11
  import { loadScenarios, parseViewpointOverview, ScenarioInfo, ViewpointEntry } from './parse';
12
12
  import {
13
13
  loadCatalog, viewpointGate, assertionDepth, dataThemesFor, coverageBalance, duplicateClusters, traceability, claimProof, taxonomyLint,
14
- GateResult, DepthResult, BalanceResult, DuplicateResult, TraceResult, ClaimProofResult, TaxonomyResult,
14
+ GateResult, DepthResult, BalanceResult, DuplicateResult, TraceResult, ClaimProofResult, TaxonomyResult, Catalog,
15
15
  } from './sensors';
16
16
  import { readIntent, projectRootFromScreenDir, IntentProfile } from './intent';
17
17
  import { getProvenance, Provenance } from './provenance';
@@ -19,6 +19,9 @@ import { specCoverage, SpecCoverageResult, parseSpecClauses } from './spec-cover
19
19
  import { downstreamScope, manualOracle, readText, DownstreamResult, ManualOracleResult,
20
20
  negativeSideEffect, sourceBacked, crossArtifactOwnership } from './quality-gates';
21
21
  import { viewpointLedger, parseViewpointItems, LedgerResult } from './viewpoint-ledger';
22
+ import { capabilityRegistry } from '../capabilities/registry';
23
+ import { discoverAndRegisterCapabilities } from '../capabilities/discover';
24
+ import { contextRouter } from '../capabilities/context-router';
22
25
 
23
26
  export interface AuditReport {
24
27
  screen: string;
@@ -63,13 +66,23 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
63
66
 
64
67
  const scenarios: ScenarioInfo[] = loadScenarios(featurePath);
65
68
  const viewpoints: ViewpointEntry[] = parseViewpointOverview(viewpointPath);
66
- const catalog = loadCatalog();
69
+ // The viewpoint catalog is owned by the default (UI) capability via the SPI; falls back to the
70
+ // in-core loader if no capability provides one. Same catalog content → identical scores (R2).
71
+ discoverAndRegisterCapabilities();
72
+ const defaultCap = capabilityRegistry.defaultCapabilityId();
73
+ const catalog = ((defaultCap && capabilityRegistry.get(defaultCap)?.viewpoints?.()) as Catalog | undefined) || loadCatalog();
67
74
  const spec = specCoverage(specPath, scenarios, featureText);
68
75
 
69
- const gate = viewpointGate(scenarios, viewpoints, catalog);
70
76
  // P3 — intent profile from qa/context.md drives the depth threshold (focus).
71
77
  const intent = readIntent(projectRootFromScreenDir(screenDir));
72
- const depth = assertionDepth(scenarios, dataThemesFor(catalog, gate.pageType), intent.focus);
78
+ // The viewpoint coverage gate + assertion depth are owned by the default (UI) capability and
79
+ // obtained via its `gateProvider` (R2.2b). Same functions underneath → byte-identical gate/depth
80
+ // → identical score. Falls back to the in-core functions if no capability provides them.
81
+ const uiGate = (defaultCap && capabilityRegistry.get(defaultCap)?.gateProvider) as
82
+ ((i: { scenarios: ScenarioInfo[]; viewpoints: ViewpointEntry[]; catalog: Catalog; focus: typeof intent.focus }) => { gate: GateResult; depth: DepthResult }) | undefined;
83
+ const provided = uiGate?.({ scenarios, viewpoints, catalog, focus: intent.focus });
84
+ const gate = provided?.gate ?? viewpointGate(scenarios, viewpoints, catalog);
85
+ const depth = provided?.depth ?? assertionDepth(scenarios, dataThemesFor(catalog, gate.pageType), intent.focus);
73
86
  const claim = claimProof(scenarios, intent.focus);
74
87
  const taxonomy = taxonomyLint(scenarios);
75
88
  const balance = coverageBalance(scenarios);
@@ -125,9 +138,7 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
125
138
  if (trace.mappedRatio < 0.5) {
126
139
  findings.push(`TRACE: ${trace.note}`);
127
140
  }
128
- if (gate.universalGaps.length) {
129
- findings.push(`UNIVERSAL: missing theme(s): ${gate.universalGaps.join(', ')} (low priority reminder).`);
130
- }
141
+ // (UNIVERSAL viewpoint-gap finding now emitted by the `ui` gate sensor — see the gate block below.)
131
142
  for (const g of spec.triggerGaps) {
132
143
  findings.push(`TRIGGER-UNCOVERED: spec validates "${g.constraint}"${g.code ? ` (${g.code})` : ''} on [${g.required.join(', ')}] but scenarios only exercise it on [${g.found.join(', ') || 'none'}] → add a ${g.missing.join(', ')}-trigger scenario for this constraint (don't collapse the trigger × input matrix).`);
133
144
  }
@@ -157,6 +168,24 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
157
168
  findings.push(`UNSOURCEABLE-SCENARIO: "${u}" doesn't trace to any FR / viewpoint item — link it to a source, or tag it @exploration (not part of the official suite).`);
158
169
  }
159
170
 
171
+ // Capability gate sensors (Capability SPI): the ContextRouter scopes WHICH gate sensors run to
172
+ // the capabilities this feature actually uses — generic ('core') + the default UI + any whose
173
+ // annotation tags appear (e.g. @query). Today core+ui gate sensors are always in scope, so this
174
+ // is behaviour-identical; it bounds the set as capability-specific gate sensors (@api, …) are
175
+ // added. Each runs over the audit context; an 'error' finding fails the gate.
176
+ const featureTags = [
177
+ ...(scenarios.some((s) => s.queryRefs && s.queryRefs.length) ? ['@query'] : []),
178
+ ...(scenarios.some((s) => s.apiRefs && s.apiRefs.length) ? ['@api'] : []),
179
+ ];
180
+ const routedGateIds = contextRouter.route({ target: { kind: 'screen', id: screenName }, artifact: 'feature', tags: featureTags }).gateSensorIds;
181
+ const gateSensorFindings = capabilityRegistry.sensors('gate')
182
+ .filter((s) => routedGateIds.includes(s.id))
183
+ .flatMap((s) => s.run({ screenName, cwd: projectRootFromScreenDir(screenDir), featureText, scenarios, universalGaps: gate.universalGaps }));
184
+ // Each gate sensor's message carries its own code prefix (VERIFICATION-FAIL / UNIVERSAL / …)
185
+ // → push verbatim.
186
+ for (const f of gateSensorFindings) findings.push(f.message);
187
+ const gateSensorError = gateSensorFindings.some((f) => f.severity === 'error');
188
+
160
189
  // #8 — multi-axis calibration: a high overall must not hide a weak axis.
161
190
  const manualCompleteness = manualOracleResult.manualTotal
162
191
  ? 1 - manualOracleResult.insufficient.length / manualOracleResult.manualTotal : 1;
@@ -180,7 +209,7 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
180
209
  // Gate spans coverage (viewpoint themes), depth, claim-proof, spec-clause coverage,
181
210
  // AND taxonomy-match (scenarios must use the project's viewpoint IDs when defined).
182
211
  const gateStatus: 'PASS' | 'FAIL' =
183
- gate.gaps.length === 0 && depth.verdict !== 'fail' && claim.verdict !== 'fail' && spec.verdict !== 'fail' && !taxonomyMismatch ? 'PASS' : 'FAIL';
212
+ gate.gaps.length === 0 && depth.verdict !== 'fail' && claim.verdict !== 'fail' && spec.verdict !== 'fail' && !taxonomyMismatch && !gateSensorError ? 'PASS' : 'FAIL';
184
213
 
185
214
  return {
186
215
  screen: screenName,
@@ -1,21 +1,37 @@
1
1
  # Driver Catalog (metadata only — NO driver code is bundled here).
2
2
  # Lets Sungen RECOMMEND/RESOLVE a driver that may not be installed yet, and tells
3
- # `sungen capability add` which package to install. See docs/spec/sungen_phase2a_spec.md.
3
+ # `sungen capability add` which package to install. See docs/spec/sungen_phase2a_spec.md
4
+ # and docs/spec/sungen_packaging_spec.md (R5 — the capability SPI + npm packages).
4
5
  #
5
- # kind: platform → the runtime/codegen adapter for a target (pick ONE per project)
6
- # kind: capability an extra ability added on top of a platform (Phase 3)
7
- # unblocks: manual-reason codes (M1–M9) this driver can resolve (Phase 2b taxonomy)
6
+ # Two axes:
7
+ # kind: platform HOW tests run (runtime/codegen adapter). Pick ONE per project.
8
+ # kind: capability WHAT extra thing is verified, added on top of a platform.
9
+ # Fields:
10
+ # status: shipped → published as an npm package (R5); planned → not built yet.
11
+ # bundled: true → installed automatically (a dependency of @sun-asterisk/sungen),
12
+ # so `capability add` is unnecessary.
13
+ # unblocks: manual-reason codes (M1–M9) this driver resolves (Phase 2b taxonomy).
14
+ #
15
+ # R5 status: the three real capabilities ship as packages — @sungen/driver-ui (web UI,
16
+ # bundled as the default), @sungen/driver-db, @sungen/driver-api. The web *platform*
17
+ # entry below points at @sungen/driver-ui (the UI step vocabulary + viewpoint gate); the
18
+ # Playwright codegen *adapter* itself is still in-core (Phase 2a). Mobile + the remaining
19
+ # capabilities are planned. See the "Platform axis & mobile evolution" section of the
20
+ # packaging spec for the `sungen init --platform <web|mobile>` roadmap.
8
21
 
9
22
  drivers:
10
23
  web:
11
24
  kind: platform
12
- package: "@sungen/driver-web" # Phase 2a: bundled Playwright adapter serves this (back-compat)
13
- runtime: playwright
14
- adapter: web # registry adapter name
25
+ package: "@sungen/driver-ui" # R5: web UI capability (step patterns + viewpoint gate)
26
+ status: shipped
27
+ bundled: true # @sun-asterisk/sungen depends on it → UI works out of the box
28
+ runtime: playwright # codegen adapter still in-core (Phase 2a)
29
+ adapter: web # registry adapter name
15
30
  capabilities: ["@ui"]
16
31
  mobile:
17
32
  kind: platform
18
33
  package: "@sungen/driver-mobile"
34
+ status: planned # PoC on the feat/mobile branch (Appium / Flutter)
19
35
  runtime: appium
20
36
  adapter: mobile
21
37
  capabilities: ["@ui"]
@@ -23,35 +39,42 @@ drivers:
23
39
  api:
24
40
  kind: capability
25
41
  package: "@sungen/driver-api"
42
+ status: shipped
26
43
  capabilities: ["@api", "@apiAssert", "@hybrid"]
27
44
  unblocks: [M2]
28
- data-factory:
29
- kind: capability
30
- package: "@sungen/driver-data-factory"
31
- capabilities: ["@dataFactory"]
32
- unblocks: [M1]
33
45
  db:
34
46
  kind: capability
35
47
  package: "@sungen/driver-db"
48
+ status: shipped
36
49
  capabilities: ["@dbAssert"]
37
50
  unblocks: [M2]
51
+ data-factory:
52
+ kind: capability
53
+ package: "@sungen/driver-data-factory"
54
+ status: planned
55
+ capabilities: ["@dataFactory"]
56
+ unblocks: [M1]
38
57
  mock:
39
58
  kind: capability
40
59
  package: "@sungen/driver-mock"
60
+ status: planned
41
61
  capabilities: ["@mock", "@network"]
42
62
  unblocks: [M3]
43
63
  mail-file:
44
64
  kind: capability
45
65
  package: "@sungen/driver-mail-file"
66
+ status: planned
46
67
  capabilities: ["@mail", "@file"]
47
68
  unblocks: [M5]
48
69
  contract:
49
70
  kind: capability
50
71
  package: "@sungen/driver-contract"
72
+ status: planned
51
73
  capabilities: ["@contract"]
52
74
  unblocks: [M5]
53
75
  specialized:
54
76
  kind: capability
55
77
  package: "@sungen/driver-specialized"
78
+ status: planned
56
79
  capabilities: ["@specialized"]
57
80
  unblocks: [M6]
@@ -32,6 +32,7 @@ export interface ScenarioInfo {
32
32
  vpId?: string; // raw leading ID token of the title (project's scheme: VP0-001, MS-HP-001, VP-LIST-001)
33
33
  casesDataset?: string; // @cases:<dataset> — data-driven; one scenario expands to N row-tests
34
34
  queryRefs?: string[]; // named queries referenced by this scenario (inline `query [name]` + @query: tags)
35
+ apiRefs?: string[]; // named API endpoints referenced by this scenario (@api: tags)
35
36
  }
36
37
 
37
38
  /** Format-tolerant: is this token an ID (project's scheme), not a prose word?
@@ -102,12 +103,15 @@ function classifyScenario(sc: ParsedScenario): ScenarioInfo {
102
103
  const manual = tags.includes('@manual');
103
104
  const casesTag = tags.find((t) => t.startsWith('@cases:'));
104
105
  const casesDataset = casesTag ? casesTag.slice('@cases:'.length).trim() : undefined;
105
- // Named-query references: @query:<name> tags + inline `query [name]` step refs.
106
+ // Named-query references: @query:<name>[(overrides)] tags + inline `query [name]` step refs.
106
107
  const queryRefs = new Set<string>();
107
- for (const t of tags) if (t.startsWith('@query:')) { const n = t.slice('@query:'.length).trim(); if (n) queryRefs.add(n); }
108
+ for (const t of tags) if (t.startsWith('@query:')) { const m = t.slice('@query:'.length).match(/^([A-Za-z_][A-Za-z0-9_]*)/); if (m) queryRefs.add(m[1]); }
108
109
  for (const step of (sc.steps as ParsedStep[]) || []) {
109
110
  for (const m of (step.text || '').matchAll(/\bquery\s+\[([A-Za-z_][A-Za-z0-9_]*)\]/gi)) queryRefs.add(m[1]);
110
111
  }
112
+ // Named-API references: @api:<name>[(overrides)] tags.
113
+ const apiRefs = new Set<string>();
114
+ for (const t of tags) if (t.startsWith('@api:')) { const m = t.slice('@api:'.length).match(/^([A-Za-z_][A-Za-z0-9_]*)/); if (m) apiRefs.add(m[1]); }
111
115
  let priority: Priority = 'unknown';
112
116
  for (const t of tags) if (PRIORITY_TAGS[t]) priority = PRIORITY_TAGS[t];
113
117
 
@@ -164,6 +168,7 @@ function classifyScenario(sc: ParsedScenario): ScenarioInfo {
164
168
  vpId,
165
169
  casesDataset,
166
170
  queryRefs: queryRefs.size ? [...queryRefs] : undefined,
171
+ apiRefs: apiRefs.size ? [...apiRefs] : undefined,
167
172
  };
168
173
  }
169
174
 
package/src/index.ts ADDED
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Public API of `@sun-asterisk/sungen` — the capability SPI plus the shared compiler/harness surface
3
+ * that capability drivers (`@sungen/driver-*`) build against. Drivers import from here; core never
4
+ * imports from a driver (discovery loads them at runtime). Keep this surface small and intentional.
5
+ */
6
+
7
+ // --- Capability SPI ---
8
+ export { capabilityRegistry, CapabilityRegistry } from './capabilities/registry';
9
+ export type { CapabilityDescriptor } from './capabilities/registry';
10
+ export type { Sensor, SensorFinding, AdvisoryScanInput, GateInput } from './capabilities/sensor';
11
+ export type { Context, DiscoveryProvider, ContextMapper, GenerationUnit } from './capabilities/context';
12
+
13
+ // --- Step-pattern authoring (a driver contributes step patterns via its descriptor) ---
14
+ export type { PatternContext, StepPattern, StepTemplateData } from './generators/test-generator/patterns/types';
15
+ export type { MappedStep } from './generators/test-generator/step-mapper';
16
+ export type { ParsedStep } from './generators/gherkin-parser';
17
+ export { getPathCode, inferPath, resolvePathVariables } from './generators/test-generator/utils/path-inference';
18
+
19
+ // --- Precondition-annotation override grammar (shared by the @query / @api driver codegen) ---
20
+ export { parseQueryOverrides } from './harness/annotation-overrides';
21
+
22
+ // --- Named-query catalog (shared: the DB driver's codegen + core's data-driven advisory lint) ---
23
+ export { resolveQuery, compileQuery, lintCatalog } from './harness/query-catalog';
24
+ export type { QueryEntry } from './harness/query-catalog';
25
+
26
+ // --- Shared harness: viewpoint catalog + coverage gate / assertion depth ---
27
+ // (the UI capability's gateProvider composes these; they also back core's ingest + audit fallback)
28
+ export { loadCatalog, viewpointGate, assertionDepth, dataThemesFor } from './harness/sensors';
29
+ export type { Catalog, GateResult, DepthResult } from './harness/sensors';
30
+ export type { ScenarioInfo, ViewpointEntry } from './harness/parse';
@@ -31,9 +31,10 @@ Parse **name** from `$ARGUMENTS`. If missing, ask the user.
31
31
  **Screen**: Verify `qa/screens/<name>/` exists. If not → `/sungen:add-screen` first.
32
32
  2. Check if `.feature` file already has scenarios.
33
33
  - If yes → use `AskUserQuestion` to ask the update mode (see `sungen-tc-generation` skill — mode depends on which tiers already exist).
34
- - If no → fresh creation. Use `AskUserQuestion` to ask generation scope:
35
- - **Tier 1 — Critical & High priority** — ~10-15 scenarios/section covering happy paths, core validation, security basics **(Recommended)**
36
- - **Full coverage — All tiers at once** — generates Tier 1 + 2 + 3 in one run. Large output (~40-60 scenarios/section), best for experienced users who want complete coverage immediately
34
+ - If no → fresh creation. **Write the feature file incrementally** (successive `Write`/`Edit`, ≈10-15 scenarios per call) — never emit the whole suite in one response, or it can exceed the model's output-token cap (`API Error: Claude's response exceeded the N output token maximum`). Use `AskUserQuestion` to ask generation scope:
35
+ - **Tier 1 — Critical & High priority** — ~10-15 scenarios/section: happy paths, core validation, security basics **(Recommended)**
36
+ - **Full coverage (incremental)** — Tier 1 + 2 + 3, written tier-by-tier in batches (`Write` T1 `Edit` append T2 `Edit` append T3). Safe on any output-token budget.
37
+ - **Full coverage (single pass)** — generate everything in one go (~40-60 scenarios/section). Faster, but **only if you raised your output cap** (`CLAUDE_CODE_MAX_OUTPUT_TOKENS ≥ 64000`) — otherwise it errors mid-generation. For power users on a high-token model/config.
37
38
  3. **Read project context + screen requirements**
38
39
 
39
40
  **Project context** — check `qa/context.md` (project root, not screen-specific):
@@ -213,6 +213,7 @@ Options: `nth` `exact` `scope` `match` `variant` `frame` `contenteditable` `colu
213
213
  | `@flow` | Mark feature as E2E flow (cross-screen testing) |
214
214
  | `@cases:dataset` | Data-driven: run the scenario once per row of the `dataset` LIST in test-data → one `test()` per row |
215
215
  | `@query:name` | Database: run the named query from `database/queries.yaml` (precondition) and bind its rows to `{{name}}`; assert with `expect {{name.count}} …` + path access. Override params `@query:name(p={{v}})`. Repeatable. (Optional Data Driver — see Database verification above) |
216
+ | `@api:name` | API: run the named request from `api/apis.yaml` (precondition) and bind the response to `{{name}}`; assert with `expect {{name.status}} …` + path access (`{{name.body.<path>}}`). Override params `@api:name(p={{v}})`. Repeatable. (Optional API Driver) |
216
217
 
217
218
  ### Data-driven scenarios (`@cases`)
218
219
 
@@ -6,6 +6,9 @@ user-invocable: false
6
6
 
7
7
  ## ⚠️ Gotchas — read before generating
8
8
 
9
+ - **Write incrementally — never emit the whole suite in one response.** Build the `.feature` in batches via successive `Write`/`Edit` (≈10–15 scenarios per call). For **Full coverage**, write tier-by-tier: `Write` Tier 1 → `Edit` append Tier 2 → `Edit` append Tier 3.
10
+ → One huge `Write` can exceed the model's output-token cap → `API Error: Claude's response exceeded the N output token maximum`. Single-pass full coverage only fits when `CLAUDE_CODE_MAX_OUTPUT_TOKENS ≥ 64000`; otherwise batch. Batching also lets the audit/reviewer run per batch — higher quality.
11
+
9
12
  - `spec_figma.md` exists → read file only, **NEVER** call `mcp__figma__*`
10
13
  → PAT auth flow already done by `sungen-capture` (mode figma-pat); re-calling fails or duplicates work.
11
14
 
@@ -26,9 +26,10 @@ You are a **Senior QA Engineer**. You structure test cases by viewpoint categori
26
26
  **Screen**: Verify `qa/screens/${input:name}/` exists. If not → `/sungen-add-screen` first.
27
27
  2. Check if `.feature` already has scenarios.
28
28
  - If yes → summarize existing coverage and ask update mode (options depend on which tiers already exist — see `sungen-tc-generation` skill for details).
29
- - If no → fresh creation. Ask generation scope:
30
- - **1) Tier 1 — Critical & High priority** — ~10-15 scenarios/section covering happy paths, core validation, security basics **(Recommended)**
31
- - **2) Full coverage — All tiers at once** — generates Tier 1 + 2 + 3 in one run. Large output (~40-60 scenarios/section), best for experienced users who want complete coverage immediately
29
+ - If no → fresh creation. **Write the feature file incrementally** (successive writes/edits, ≈10-15 scenarios per call) — never emit the whole suite in one response, or it can exceed the model's output-token cap (`response exceeded the N output token maximum`). Ask generation scope:
30
+ - **1) Tier 1 — Critical & High priority** — ~10-15 scenarios/section: happy paths, core validation, security basics **(Recommended)**
31
+ - **2) Full coverage (incremental)** — Tier 1 + 2 + 3, written tier-by-tier in batches. Safe on any output-token budget.
32
+ - **3) Full coverage (single pass)** — everything in one go (~40-60 scenarios/section). Faster, but **only if you raised your output cap** (`CLAUDE_CODE_MAX_OUTPUT_TOKENS ≥ 64000`) — otherwise it errors mid-generation. For power users on a high-token model/config.
32
33
  3. **Read project context + screen requirements**
33
34
 
34
35
  **Project context** — check `qa/context.md` (project root, not screen-specific):
@@ -213,6 +213,7 @@ Options: `nth` `exact` `scope` `match` `variant` `frame` `contenteditable` `colu
213
213
  | `@flow` | Mark feature as E2E flow (cross-screen testing) |
214
214
  | `@cases:dataset` | Data-driven: run the scenario once per row of the `dataset` LIST in test-data → one `test()` per row |
215
215
  | `@query:name` | Database: run the named query from `database/queries.yaml` (precondition) and bind its rows to `{{name}}`; assert with `expect {{name.count}} …` + path access. Override params `@query:name(p={{v}})`. Repeatable. (Optional Data Driver — see Database verification above) |
216
+ | `@api:name` | API: run the named request from `api/apis.yaml` (precondition) and bind the response to `{{name}}`; assert with `expect {{name.status}} …` + path access (`{{name.body.<path>}}`). Override params `@api:name(p={{v}})`. Repeatable. (Optional API Driver) |
216
217
 
217
218
  ### Data-driven scenarios (`@cases`)
218
219
 
@@ -6,6 +6,9 @@ user-invocable: false
6
6
 
7
7
  ## ⚠️ Gotchas — read before generating
8
8
 
9
+ - **Write incrementally — never emit the whole suite in one response.** Build the `.feature` in batches via successive `Write`/`Edit` (≈10–15 scenarios per call). For **Full coverage**, write tier-by-tier: `Write` Tier 1 → `Edit` append Tier 2 → `Edit` append Tier 3.
10
+ → One huge `Write` can exceed the model's output-token cap → `API Error: Claude's response exceeded the N output token maximum`. Single-pass full coverage only fits when `CLAUDE_CODE_MAX_OUTPUT_TOKENS ≥ 64000`; otherwise batch. Batching also lets the audit/reviewer run per batch — higher quality.
11
+
9
12
  - `spec_figma.md` exists → read file only, **NEVER** call `mcp__figma__*`
10
13
  → PAT auth flow already done by `sungen-capture` (mode figma-pat); re-calling fails or duplicates work.
11
14
 
@@ -0,0 +1,101 @@
1
+ /* eslint-disable */
2
+ /**
3
+ * Sungen API Driver — runtime helper (auto-generated into specs/api.ts).
4
+ *
5
+ * Runs a catalog-defined HTTP request and returns { status, ok, body, headers } — bound to a
6
+ * `{{name}}` variable by the `@api:<name>` annotation, asserted with `expect {{name.status}} …` /
7
+ * `{{name.body.<path>}}`. Base URL + auth come from a `kind: api` datasource in datasources.yaml,
8
+ * with `${VAR}` resolved from .env.qa / process.env — never inline.
9
+ *
10
+ * Safety: a datasource flagged `env: production` is refused unless SUNGEN_ALLOW_PROD=1.
11
+ * DO NOT EDIT — regenerated by `sungen generate`.
12
+ */
13
+ import * as fs from 'fs';
14
+ import * as path from 'path';
15
+
16
+ interface ApiDataSource {
17
+ kind?: string;
18
+ base_url?: string;
19
+ baseUrl?: string;
20
+ env?: string;
21
+ headers?: Record<string, string>;
22
+ timeout_ms?: number;
23
+ }
24
+
25
+ function loadEnvQa(): void {
26
+ for (const name of ['.env.qa', `.env.qa.${process.env.SUNGEN_ENV || ''}`]) {
27
+ const p = path.join(process.cwd(), name);
28
+ if (!name.endsWith('.') && fs.existsSync(p)) {
29
+ for (const line of fs.readFileSync(p, 'utf8').split('\n')) {
30
+ const m = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/);
31
+ if (m && process.env[m[1]] === undefined) process.env[m[1]] = m[2].replace(/^["']|["']$/g, '');
32
+ }
33
+ }
34
+ }
35
+ }
36
+
37
+ function loadConfig(): Record<string, ApiDataSource> {
38
+ loadEnvQa();
39
+ const file = [path.join(process.cwd(), 'datasources.yaml'), path.join(process.cwd(), 'qa', 'datasources.yaml')].find((f) => fs.existsSync(f));
40
+ if (!file) throw new Error('API Driver: no datasources.yaml found (project root or qa/).');
41
+ const raw = fs.readFileSync(file, 'utf8').replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (_, k) => process.env[k] ?? '');
42
+ const { parse } = require('yaml');
43
+ const doc = parse(raw) || {};
44
+ return doc.datasources || {};
45
+ }
46
+
47
+ function substitute(text: string, params: Record<string, any>): string {
48
+ return text.replace(/:([A-Za-z_][A-Za-z0-9_]*)/g, (_m, p) => encodeURIComponent(String(params[p] ?? '')));
49
+ }
50
+
51
+ class ApiClient {
52
+ private configs: Record<string, ApiDataSource> | null = null;
53
+
54
+ private cfg(name?: string): { key: string; conf: ApiDataSource } {
55
+ if (!this.configs) this.configs = loadConfig();
56
+ const key = name || Object.keys(this.configs).find((k) => (this.configs![k].kind || 'api') === 'api') || Object.keys(this.configs)[0];
57
+ const conf = this.configs[key];
58
+ if (!conf) throw new Error(`API Driver: datasource "${key}" not found in datasources.yaml`);
59
+ if (conf.env === 'production' && process.env.SUNGEN_ALLOW_PROD !== '1') {
60
+ throw new Error(`API Driver: datasource "${key}" is env: production — refused (set SUNGEN_ALLOW_PROD=1 to override).`);
61
+ }
62
+ return { key, conf };
63
+ }
64
+
65
+ /** Run a catalog request and return the response. `req` is embedded at compile time; `params` bind at runtime. */
66
+ async call(
67
+ label: string,
68
+ req: { method: string; path: string; body?: unknown; datasource?: string },
69
+ params: Record<string, any> = {},
70
+ ): Promise<{ status: number; ok: boolean; body: any; headers: Record<string, string> }> {
71
+ const { conf } = this.cfg(req.datasource);
72
+ const base = (conf.base_url || conf.baseUrl || '').replace(/\/$/, '');
73
+ if (!base) throw new Error(`API Driver: ${label} — datasource has no base_url (set it in .env.qa).`);
74
+ const url = base + substitute(req.path, params);
75
+
76
+ let body: string | undefined;
77
+ const headers: Record<string, string> = { ...(conf.headers || {}) };
78
+ if (req.body !== undefined && req.body !== null) {
79
+ const resolved = JSON.parse(JSON.stringify(req.body).replace(/":([A-Za-z_][A-Za-z0-9_]*)"/g, (_m, p) => JSON.stringify(params[p] ?? null)));
80
+ body = JSON.stringify(resolved);
81
+ if (!headers['content-type'] && !headers['Content-Type']) headers['content-type'] = 'application/json';
82
+ }
83
+
84
+ const controller = new AbortController();
85
+ const timer = setTimeout(() => controller.abort(), conf.timeout_ms ?? 15000);
86
+ let res: Response;
87
+ try {
88
+ res = await fetch(url, { method: req.method, headers, body, signal: controller.signal });
89
+ } finally {
90
+ clearTimeout(timer);
91
+ }
92
+ const text = await res.text();
93
+ let parsed: any = text;
94
+ try { parsed = text ? JSON.parse(text) : null; } catch { /* non-JSON → keep text */ }
95
+ const outHeaders: Record<string, string> = {};
96
+ res.headers.forEach((v, k) => { outHeaders[k] = v; });
97
+ return { status: res.status, ok: res.ok, body: parsed, headers: outHeaders };
98
+ }
99
+ }
100
+
101
+ export const api = new ApiClient();
@@ -1,7 +0,0 @@
1
- import { StepPattern } from './types';
2
- /**
3
- * Assertion patterns: visibility, text content, state, attributes
4
- * Uses template engine for framework-agnostic code generation
5
- */
6
- export declare const assertionPatterns: StepPattern[];
7
- //# sourceMappingURL=assertion-patterns.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"assertion-patterns.d.ts","sourceRoot":"","sources":["../../../../src/generators/test-generator/patterns/assertion-patterns.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAoB,MAAM,SAAS,CAAC;AAExD;;;GAGG;AACH,eAAO,MAAM,iBAAiB,EAAE,WAAW,EA2qB1C,CAAC"}