@sun-asterisk/sungen 3.2.16-beta.1 → 3.2.16-beta.10

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 (140) hide show
  1. package/dist/cli/commands/delivery.d.ts.map +1 -1
  2. package/dist/cli/commands/delivery.js +67 -20
  3. package/dist/cli/commands/delivery.js.map +1 -1
  4. package/dist/exporters/feature-parser.d.ts +16 -1
  5. package/dist/exporters/feature-parser.d.ts.map +1 -1
  6. package/dist/exporters/feature-parser.js +21 -2
  7. package/dist/exporters/feature-parser.js.map +1 -1
  8. package/dist/exporters/matrix/build.d.ts +27 -2
  9. package/dist/exporters/matrix/build.d.ts.map +1 -1
  10. package/dist/exporters/matrix/build.js +277 -47
  11. package/dist/exporters/matrix/build.js.map +1 -1
  12. package/dist/exporters/matrix/export.d.ts +7 -4
  13. package/dist/exporters/matrix/export.d.ts.map +1 -1
  14. package/dist/exporters/matrix/export.js +22 -6
  15. package/dist/exporters/matrix/export.js.map +1 -1
  16. package/dist/exporters/matrix/gates.d.ts.map +1 -1
  17. package/dist/exporters/matrix/gates.js +112 -3
  18. package/dist/exporters/matrix/gates.js.map +1 -1
  19. package/dist/exporters/matrix/map-loader.d.ts.map +1 -1
  20. package/dist/exporters/matrix/map-loader.js +20 -0
  21. package/dist/exporters/matrix/map-loader.js.map +1 -1
  22. package/dist/exporters/matrix/render-csv.d.ts +3 -2
  23. package/dist/exporters/matrix/render-csv.d.ts.map +1 -1
  24. package/dist/exporters/matrix/render-csv.js +53 -30
  25. package/dist/exporters/matrix/render-csv.js.map +1 -1
  26. package/dist/exporters/matrix/render-xlsx.d.ts +32 -8
  27. package/dist/exporters/matrix/render-xlsx.d.ts.map +1 -1
  28. package/dist/exporters/matrix/render-xlsx.js +215 -83
  29. package/dist/exporters/matrix/render-xlsx.js.map +1 -1
  30. package/dist/exporters/matrix/types.d.ts +53 -7
  31. package/dist/exporters/matrix/types.d.ts.map +1 -1
  32. package/dist/exporters/matrix/types.js +2 -2
  33. package/dist/exporters/matrix/types.js.map +1 -1
  34. package/dist/exporters/matrix/wording.d.ts +61 -0
  35. package/dist/exporters/matrix/wording.d.ts.map +1 -0
  36. package/dist/exporters/matrix/wording.js +221 -0
  37. package/dist/exporters/matrix/wording.js.map +1 -0
  38. package/dist/exporters/scenario-merger.d.ts.map +1 -1
  39. package/dist/exporters/scenario-merger.js +2 -1
  40. package/dist/exporters/scenario-merger.js.map +1 -1
  41. package/dist/exporters/spec-parser.d.ts.map +1 -1
  42. package/dist/exporters/spec-parser.js +2 -1
  43. package/dist/exporters/spec-parser.js.map +1 -1
  44. package/dist/harness/audit.d.ts.map +1 -1
  45. package/dist/harness/audit.js +11 -2
  46. package/dist/harness/audit.js.map +1 -1
  47. package/dist/harness/blindspot.d.ts.map +1 -1
  48. package/dist/harness/blindspot.js +2 -1
  49. package/dist/harness/blindspot.js.map +1 -1
  50. package/dist/harness/capability-plan.d.ts.map +1 -1
  51. package/dist/harness/capability-plan.js +3 -2
  52. package/dist/harness/capability-plan.js.map +1 -1
  53. package/dist/harness/feedback.d.ts.map +1 -1
  54. package/dist/harness/feedback.js +3 -2
  55. package/dist/harness/feedback.js.map +1 -1
  56. package/dist/harness/flow-check.d.ts.map +1 -1
  57. package/dist/harness/flow-check.js +2 -1
  58. package/dist/harness/flow-check.js.map +1 -1
  59. package/dist/harness/flow-plan.d.ts.map +1 -1
  60. package/dist/harness/flow-plan.js +3 -2
  61. package/dist/harness/flow-plan.js.map +1 -1
  62. package/dist/harness/intent.d.ts.map +1 -1
  63. package/dist/harness/intent.js +2 -1
  64. package/dist/harness/intent.js.map +1 -1
  65. package/dist/harness/journey.d.ts.map +1 -1
  66. package/dist/harness/journey.js +3 -2
  67. package/dist/harness/journey.js.map +1 -1
  68. package/dist/harness/ledger.d.ts.map +1 -1
  69. package/dist/harness/ledger.js +3 -2
  70. package/dist/harness/ledger.js.map +1 -1
  71. package/dist/harness/manifest.d.ts.map +1 -1
  72. package/dist/harness/manifest.js +4 -3
  73. package/dist/harness/manifest.js.map +1 -1
  74. package/dist/harness/parse.d.ts.map +1 -1
  75. package/dist/harness/parse.js +16 -3
  76. package/dist/harness/parse.js.map +1 -1
  77. package/dist/harness/quality-gates.d.ts.map +1 -1
  78. package/dist/harness/quality-gates.js +2 -1
  79. package/dist/harness/quality-gates.js.map +1 -1
  80. package/dist/harness/read-text.d.ts +22 -0
  81. package/dist/harness/read-text.d.ts.map +1 -0
  82. package/dist/harness/read-text.js +64 -0
  83. package/dist/harness/read-text.js.map +1 -0
  84. package/dist/harness/script-check.d.ts.map +1 -1
  85. package/dist/harness/script-check.js +3 -2
  86. package/dist/harness/script-check.js.map +1 -1
  87. package/dist/harness/sensors.d.ts.map +1 -1
  88. package/dist/harness/sensors.js +2 -10
  89. package/dist/harness/sensors.js.map +1 -1
  90. package/dist/harness/spec-coverage.d.ts +5 -0
  91. package/dist/harness/spec-coverage.d.ts.map +1 -1
  92. package/dist/harness/spec-coverage.js +17 -7
  93. package/dist/harness/spec-coverage.js.map +1 -1
  94. package/dist/harness/trace.d.ts.map +1 -1
  95. package/dist/harness/trace.js +4 -3
  96. package/dist/harness/trace.js.map +1 -1
  97. package/dist/harness/viewpoint-ledger.d.ts.map +1 -1
  98. package/dist/harness/viewpoint-ledger.js +2 -1
  99. package/dist/harness/viewpoint-ledger.js.map +1 -1
  100. package/dist/orchestrator/templates/ai-src/commands/create-test.md +9 -0
  101. package/dist/orchestrator/templates/ai-src/commands/delivery.md +103 -18
  102. package/dist/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +77 -11
  103. package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +1 -0
  104. package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +22 -0
  105. package/package.json +4 -4
  106. package/src/cli/commands/delivery.ts +68 -22
  107. package/src/exporters/feature-parser.ts +21 -2
  108. package/src/exporters/matrix/build.ts +281 -43
  109. package/src/exporters/matrix/export.ts +31 -6
  110. package/src/exporters/matrix/gates.ts +119 -3
  111. package/src/exporters/matrix/map-loader.ts +22 -1
  112. package/src/exporters/matrix/render-csv.ts +53 -30
  113. package/src/exporters/matrix/render-xlsx.ts +216 -85
  114. package/src/exporters/matrix/types.ts +58 -8
  115. package/src/exporters/matrix/wording.ts +221 -0
  116. package/src/exporters/scenario-merger.ts +2 -1
  117. package/src/exporters/spec-parser.ts +2 -1
  118. package/src/harness/audit.ts +11 -2
  119. package/src/harness/blindspot.ts +2 -1
  120. package/src/harness/capability-plan.ts +3 -2
  121. package/src/harness/feedback.ts +3 -2
  122. package/src/harness/flow-check.ts +2 -1
  123. package/src/harness/flow-plan.ts +3 -2
  124. package/src/harness/intent.ts +2 -1
  125. package/src/harness/journey.ts +3 -2
  126. package/src/harness/ledger.ts +3 -2
  127. package/src/harness/manifest.ts +4 -3
  128. package/src/harness/parse.ts +17 -3
  129. package/src/harness/quality-gates.ts +2 -1
  130. package/src/harness/read-text.ts +28 -0
  131. package/src/harness/script-check.ts +3 -2
  132. package/src/harness/sensors.ts +2 -1
  133. package/src/harness/spec-coverage.ts +22 -7
  134. package/src/harness/trace.ts +4 -3
  135. package/src/harness/viewpoint-ledger.ts +2 -1
  136. package/src/orchestrator/templates/ai-src/commands/create-test.md +9 -0
  137. package/src/orchestrator/templates/ai-src/commands/delivery.md +103 -18
  138. package/src/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +77 -11
  139. package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +1 -0
  140. package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +22 -0
@@ -31,6 +31,7 @@ import {
31
31
  loadMatrixModel,
32
32
  approveMatrix,
33
33
  writeMatrixDeliverables,
34
+ MatrixFormat,
34
35
  hasErrors,
35
36
  hasReview,
36
37
  } from '../../exporters/matrix/export';
@@ -621,6 +622,7 @@ function matrixPathsFor(cwd: string, target: DeliveryTarget): MatrixTargetPaths
621
622
  featureFile: path.join(base, 'features', `${target.featureBaseName}.feature`),
622
623
  testDataFile: resolveTestDataPathForTarget(cwd, target),
623
624
  specFile: path.join(genBase, `${target.featureBaseName}.spec.ts`),
625
+ specMdFile: path.join(base, 'requirements', 'spec.md'),
624
626
  resultsPath: resolveResultsPath(cwd, target),
625
627
  mapFile: mapFilePath(base, target.featureBaseName),
626
628
  };
@@ -650,7 +652,7 @@ interface MatrixRunSummary {
650
652
  async function exportMatrixTarget(
651
653
  cwd: string,
652
654
  target: DeliveryTarget,
653
- opts: { check?: boolean; preview?: boolean },
655
+ opts: { check?: boolean; preview?: boolean; format?: MatrixFormat },
654
656
  ): Promise<MatrixRunSummary | null> {
655
657
  const paths = matrixPathsFor(cwd, target);
656
658
  const { model, mapMissing, mapErrors } = loadMatrixModel(paths);
@@ -688,24 +690,38 @@ async function exportMatrixTarget(
688
690
  return null;
689
691
  }
690
692
 
691
- const { csvPath, xlsxPath } = await writeMatrixDeliverables(paths, model);
693
+ const { csvPath, xlsxPath } = await writeMatrixDeliverables(paths, model, opts.format ?? 'xlsx');
692
694
  const passed = model.items.reduce((a, i) => a + i.resultCounts.passed, 0);
693
695
  const failed = model.items.reduce((a, i) => a + i.resultCounts.failed, 0);
694
- log(` ${COLOR.green}→ ${path.relative(cwd, xlsxPath)}${COLOR.reset} ${COLOR.gray}(+ ${path.relative(cwd, csvPath)})${COLOR.reset}`);
696
+ const written = [xlsxPath, csvPath].filter((p): p is string => !!p).map((p) => path.relative(cwd, p));
697
+ for (const rel of written) log(` ${COLOR.green}→ ${rel}${COLOR.reset}`);
695
698
  return {
696
699
  label: paths.label,
697
700
  items: model.manifest.itemCount,
698
701
  variants: model.manifest.variantCount,
699
702
  passed,
700
703
  failed,
701
- file: path.relative(cwd, xlsxPath),
704
+ file: written.join(' + '),
702
705
  };
703
706
  }
704
707
 
708
+ /**
709
+ * One place that accounts for every target the run did NOT write, so a sweep is
710
+ * never silently partial: scaffolds skipped by preflight and units whose gates /
711
+ * missing map stopped the export.
712
+ */
713
+ function printSkipSummary(skipped: string[], notExported: string[]): void {
714
+ if (skipped.length === 0 && notExported.length === 0) return;
715
+ log(`${COLOR.gray}Not exported (${skipped.length + notExported.length}):${COLOR.reset}`);
716
+ for (const s of skipped) log(` ${COLOR.gray}· ${s}${COLOR.reset}`);
717
+ for (const n of notExported) log(` ${COLOR.gray}· ${n} (see the message above)${COLOR.reset}`);
718
+ log('');
719
+ }
720
+
705
721
  function printMatrixSummaryTable(summaries: MatrixRunSummary[]): void {
706
722
  log(`\n${COLOR.bold}Delivery matrix export complete${COLOR.reset}\n`);
707
723
  const colWidth = Math.max(20, ...summaries.map((s) => s.label.length)) + 1;
708
- log(` ${'Feature'.padEnd(colWidth)}Items Variants Passed Failed File`);
724
+ log(` ${'Feature'.padEnd(colWidth)}Items Variants Passed Failed File(s)`);
709
725
  log(' ' + '-'.repeat(colWidth + 50));
710
726
  for (const s of summaries) {
711
727
  log(
@@ -795,9 +811,11 @@ export function registerDeliveryCommand(program: Command): void {
795
811
  .option('--check', 'Matrix gates only — validate the delivery map, write nothing')
796
812
  .option('--approve [ids]', 'Approve proposed groups (all, or a comma-separated id list) + stamp fingerprints')
797
813
  .option('--preview', 'Render the matrix despite review-required findings (DRAFT watermark)')
814
+ .option('--format <format>', 'Matrix output format: xlsx (default) | csv | both', 'xlsx')
798
815
  .action(async (names: string[], options: {
799
816
  skipPreflight?: boolean; continueOnMissing?: boolean; env?: string;
800
817
  legacy?: boolean; full?: boolean; check?: boolean; approve?: boolean | string; preview?: boolean;
818
+ format?: string;
801
819
  }) => {
802
820
  try {
803
821
  // Same effect as running with SUNGEN_ENV=<env> — result-file resolution,
@@ -805,11 +823,19 @@ export function registerDeliveryCommand(program: Command): void {
805
823
  if (options.env) process.env.SUNGEN_ENV = options.env;
806
824
  const cwd = process.cwd();
807
825
 
826
+ // Matrix output format — one artifact by default (xlsx); csv/both on request.
827
+ const format = (options.format ?? 'xlsx').toLowerCase();
828
+ if (!['xlsx', 'csv', 'both'].includes(format)) {
829
+ console.error(`${COLOR.red}Invalid --format "${options.format}"${COLOR.reset} — use xlsx | csv | both`);
830
+ process.exit(1);
831
+ }
832
+
808
833
  // 1. Scope detection — each positional name expands into one target
809
834
  // per `.feature` file inside that screen/flow. Passing a specific
810
835
  // feature basename (e.g. `home-modal`) narrows to that one file.
811
836
  let targets: DeliveryTarget[];
812
- if (names && names.length > 0) {
837
+ const explicitScope = !!(names && names.length > 0);
838
+ if (explicitScope) {
813
839
  targets = names.flatMap((n) => resolveTargetsFromArg(cwd, n));
814
840
  } else {
815
841
  targets = listAllTargets(cwd);
@@ -822,8 +848,13 @@ export function registerDeliveryCommand(program: Command): void {
822
848
  const labels = targets.map((t) => t.isFlow ? `flow/${t.featureBaseName}` : t.featureBaseName);
823
849
  log(`${COLOR.bold}sungen delivery${COLOR.reset} — exporting ${targets.length} feature(s): ${labels.join(', ')}\n`);
824
850
 
825
- // 2. Pre-flight
851
+ // 2. Pre-flight — strictness follows the SCOPE. Naming a unit means "I want
852
+ // this one" (fail loudly); a no-argument sweep means "export everything
853
+ // that is ready", so scaffolded units (added but not yet authored) are
854
+ // skipped with a note instead of aborting the whole run — otherwise one
855
+ // untouched scaffold in a work-in-progress project produces zero files.
826
856
  let toExport: DeliveryTarget[];
857
+ const skipped: string[] = [];
827
858
  if (options.skipPreflight) {
828
859
  toExport = targets;
829
860
  } else {
@@ -832,22 +863,22 @@ export function registerDeliveryCommand(program: Command): void {
832
863
 
833
864
  const blockers = checks.filter(hasBlockingMissing);
834
865
  if (blockers.length > 0) {
835
- if (options.continueOnMissing) {
836
- const passedScreens = new Set(
837
- checks.filter((c) => !hasBlockingMissing(c)).map((c) => c.screen)
838
- );
839
- toExport = targets.filter((t) => {
840
- const label = t.isFlow ? `flow/${t.featureBaseName}` : t.featureBaseName;
841
- return passedScreens.has(label);
842
- });
843
- log(`${COLOR.yellow}Continuing with ${toExport.length} ready target(s).${COLOR.reset}\n`);
844
- } else {
866
+ if (explicitScope && !options.continueOnMissing) {
845
867
  console.error(
846
- `${COLOR.red}Aborted:${COLOR.reset} ${blockers.length} target(s) have blocking issues.\n` +
868
+ `${COLOR.red}Aborted:${COLOR.reset} ${blockers.length} named target(s) have blocking issues.\n` +
847
869
  `Run the suggested commands above, or use ${COLOR.cyan}--continue-on-missing${COLOR.reset} to skip them.`
848
870
  );
849
871
  process.exit(1);
850
872
  }
873
+ const readyLabels = new Set(
874
+ checks.filter((c) => !hasBlockingMissing(c)).map((c) => c.screen)
875
+ );
876
+ toExport = targets.filter((t) => {
877
+ const label = t.isFlow ? `flow/${t.featureBaseName}` : t.featureBaseName;
878
+ return readyLabels.has(label);
879
+ });
880
+ for (const b of blockers) skipped.push(`${b.screen} (not authored yet — run /sungen:create-test)`);
881
+ log(`${COLOR.yellow}Skipping ${blockers.length} target(s) that have no test cases yet; continuing with ${toExport.length}.${COLOR.reset}\n`);
851
882
  } else {
852
883
  toExport = targets;
853
884
  }
@@ -883,14 +914,25 @@ export function registerDeliveryCommand(program: Command): void {
883
914
  }
884
915
 
885
916
  const matrixSummaries: MatrixRunSummary[] = [];
886
- let failedTargets = 0;
917
+ const notExported: string[] = [];
887
918
  for (const target of toExport) {
888
- const s = await exportMatrixTarget(cwd, target, { check: options.check, preview: options.preview });
919
+ const s = await exportMatrixTarget(cwd, target, {
920
+ check: options.check, preview: options.preview, format: format as MatrixFormat,
921
+ });
889
922
  if (s) matrixSummaries.push(s);
890
- else failedTargets++;
923
+ else notExported.push(target.isFlow ? `flow/${target.featureBaseName}` : target.featureBaseName);
891
924
  }
892
925
  if (matrixSummaries.length > 0 && !options.check) printMatrixSummaryTable(matrixSummaries);
893
- if (failedTargets > 0) process.exit(1);
926
+ printSkipSummary(skipped, notExported);
927
+ // A named target that produced nothing is a failure. A sweep is a failure
928
+ // only when it produced nothing at all — units still waiting for a map or
929
+ // an approval are the normal state of a live project. Preflight skips are
930
+ // not counted here: reaching this point in an explicit scope means
931
+ // --continue-on-missing was passed, which IS the opt-in to tolerate them.
932
+ const failed = explicitScope
933
+ ? notExported.length > 0
934
+ : matrixSummaries.length === 0;
935
+ if (failed) process.exit(1);
894
936
  return;
895
937
  }
896
938
 
@@ -903,6 +945,10 @@ export function registerDeliveryCommand(program: Command): void {
903
945
 
904
946
  // 4. Summary
905
947
  printSummaryTable(summaries);
948
+ printSkipSummary(skipped, []);
949
+ // Same rule as the matrix path: every attempted target in an explicit scope
950
+ // must have produced a file; a sweep only fails when nothing was written.
951
+ if (explicitScope ? summaries.length < toExport.length : summaries.length === 0) process.exit(1);
906
952
  } catch (err) {
907
953
  console.error(`${COLOR.red}Fatal:${COLOR.reset} ${err instanceof Error ? err.message : err}`);
908
954
  process.exit(1);
@@ -172,12 +172,28 @@ export function extractAuthRole(tags: string[]): string | null {
172
172
  }
173
173
 
174
174
  /**
175
- * Extract VP ID and human-readable name from scenario name like
175
+ * The leading viewpoint/requirement id of a scenario title.
176
+ *
177
+ * The `VP-` prefix is the scheme sungen suggests, NOT a requirement: real projects
178
+ * bring their own (`SEC-123`, `PER-345`, `MS-HP-001`, `VP0-001`), and the harness
179
+ * has always been format-tolerant about it. The exporters were not — a project
180
+ * using `SEC-123` got no id at all, which made every scenario "unaddressable" and
181
+ * blocked the whole delivery matrix at Gate A.
182
+ *
183
+ * Shape: uppercase segments joined by `-`, ending in a number (with an optional
184
+ * letter or sub-code suffix). Requiring the trailing number is what keeps an
185
+ * ordinary title word ("A11y check…") from being mistaken for an id.
186
+ */
187
+ export const SCENARIO_ID = /^([A-Z][A-Z0-9]*(?:-[A-Z0-9]+)*-\d+(?:[a-zA-Z]|-[A-Z0-9]+)?)/;
188
+
189
+ /**
190
+ * Extract the id and the human-readable name from a scenario title:
176
191
  * "VP-UI-001 Modal displays all required fields" →
177
192
  * { vpId: "VP-UI-001", category1: "Modal displays all required fields" }
193
+ * "SEC-123 Login is protected" → { vpId: "SEC-123", category1: "Login is protected" }
178
194
  */
179
195
  export function splitVpAndName(scenarioName: string): { vpId?: string; category1: string } {
180
- const match = scenarioName.match(/^(VP-[A-Z][A-Z0-9]*-\d+(?:[a-zA-Z]|-[A-Z0-9]+)?)\s+(.+)$/);
196
+ const match = scenarioName.match(new RegExp(SCENARIO_ID.source + '\\s+(.+)$'));
181
197
  if (match) {
182
198
  return { vpId: match[1], category1: match[2] };
183
199
  }
@@ -194,6 +210,9 @@ export function splitVpAndName(scenarioName: string): { vpId?: string; category1
194
210
  */
195
211
  export function mapVpToCategory2(vpId: string | undefined, scenarioName?: string): string {
196
212
  if (!vpId) return 'Function';
213
+ // Compare on the category segment so a project scheme without the `VP-` prefix
214
+ // (`SEC-123`) classifies the same as `VP-SEC-001`.
215
+ vpId = /^VP-/.test(vpId) ? vpId : `VP-${vpId}`;
197
216
  if (vpId.startsWith('VP-SEC-')) {
198
217
  if (scenarioName && /xss|injection/i.test(scenarioName)) return 'Function';
199
218
  return 'Accessing';