@sun-asterisk/sungen 3.2.16-beta.11 → 3.2.16-beta.12
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/dist/cli/index.js +5 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/orchestrator/assets-drift.d.ts +24 -0
- package/dist/orchestrator/assets-drift.d.ts.map +1 -0
- package/dist/orchestrator/assets-drift.js +80 -0
- package/dist/orchestrator/assets-drift.js.map +1 -0
- package/dist/orchestrator/templates/ai-src/commands/create-test.md +20 -9
- package/package.json +3 -3
- package/src/cli/index.ts +6 -0
- package/src/orchestrator/assets-drift.ts +64 -0
- package/src/orchestrator/templates/ai-src/commands/create-test.md +20 -9
package/dist/cli/index.js
CHANGED
|
@@ -36,6 +36,7 @@ const repair_1 = require("./commands/repair");
|
|
|
36
36
|
const login_1 = require("./commands/login");
|
|
37
37
|
const registry_1 = require("../capabilities/registry");
|
|
38
38
|
const discover_1 = require("../capabilities/discover");
|
|
39
|
+
const assets_drift_1 = require("../orchestrator/assets-drift");
|
|
39
40
|
// Read version from package.json so `--version` never drifts from the released version.
|
|
40
41
|
const { version } = require('../../package.json');
|
|
41
42
|
async function main() {
|
|
@@ -83,6 +84,10 @@ async function main() {
|
|
|
83
84
|
for (const registerCommand of cap.cliCommands ?? [])
|
|
84
85
|
registerCommand(program);
|
|
85
86
|
}
|
|
87
|
+
// Every command warns once when the project's AI assets came from a different
|
|
88
|
+
// build — `update` is the only thing that refreshes them, and nothing used to
|
|
89
|
+
// say so (stderr, so `--json` output stays parseable).
|
|
90
|
+
program.hook('preAction', () => { (0, assets_drift_1.warnAssetsDrift)(process.cwd()); });
|
|
86
91
|
await program.parseAsync(process.argv);
|
|
87
92
|
}
|
|
88
93
|
main().catch((error) => {
|
package/dist/cli/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";;AACA;;;GAGG;;AAEH,yCAAoC;AACpC,0CAAsD;AACtD,wCAAoD;AACpD,kDAA8D;AAC9D,kDAA8D;AAC9D,8CAA0D;AAC1D,kDAA8D;AAC9D,4CAAwD;AACxD,kDAA6D;AAC7D,oDAAgE;AAChE,4CAAwD;AACxD,sDAAiE;AACjE,gDAA4D;AAC5D,0CAAsD;AACtD,8CAA0D;AAC1D,0CAAsD;AACtD,kDAA8D;AAC9D,8CAA0D;AAC1D,kDAA8D;AAC9D,wDAAmE;AACnE,0DAAqE;AACrE,4CAAwD;AACxD,oDAAgE;AAChE,oDAAgE;AAChE,sDAAkE;AAClE,sDAAiE;AACjE,gDAA4D;AAC5D,8CAA0D;AAC1D,4CAAwD;AACxD,uDAA8D;AAC9D,uDAA2E;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Are this project's AI assets (commands + skills under `.claude/` etc.) the ones
|
|
3
|
+
* the running sungen would write?
|
|
4
|
+
*
|
|
5
|
+
* Upgrading the npm package does NOT refresh them — `sungen update` does. Nothing
|
|
6
|
+
* said so, so a project kept running prompts from an older build indefinitely: a
|
|
7
|
+
* QA upgraded, re-ran `/sungen:create-test`, and got the OLD command (141 lines vs
|
|
8
|
+
* the shipped 267) with none of the newer guidance. The failure is invisible
|
|
9
|
+
* because everything still "works", just to an older specification.
|
|
10
|
+
*
|
|
11
|
+
* The manifest already records the version + a hash per managed file; this only
|
|
12
|
+
* reads it and reports.
|
|
13
|
+
*/
|
|
14
|
+
export interface AssetsDrift {
|
|
15
|
+
recordedVersion: string;
|
|
16
|
+
runningVersion: string;
|
|
17
|
+
/** Managed files whose content no longer matches the manifest's hash. */
|
|
18
|
+
changed: number;
|
|
19
|
+
total: number;
|
|
20
|
+
}
|
|
21
|
+
export declare function checkAssetsDrift(cwd: string): AssetsDrift | null;
|
|
22
|
+
/** One line, on stderr so `--json` output stays machine-readable. */
|
|
23
|
+
export declare function warnAssetsDrift(cwd: string): void;
|
|
24
|
+
//# sourceMappingURL=assets-drift.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assets-drift.d.ts","sourceRoot":"","sources":["../../src/orchestrator/assets-drift.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,WAAW;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAwBhE;AAED,qEAAqE;AACrE,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAWjD"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.checkAssetsDrift = checkAssetsDrift;
|
|
37
|
+
exports.warnAssetsDrift = warnAssetsDrift;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const package_info_1 = require("../exporters/package-info");
|
|
41
|
+
function checkAssetsDrift(cwd) {
|
|
42
|
+
const manifestPath = path.join(cwd, '.sungen', 'manifest.json');
|
|
43
|
+
if (!fs.existsSync(manifestPath))
|
|
44
|
+
return null; // not a sungen project (yet)
|
|
45
|
+
let manifest;
|
|
46
|
+
try {
|
|
47
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return null; // unreadable → say nothing
|
|
51
|
+
}
|
|
52
|
+
const recordedVersion = manifest.version ?? '';
|
|
53
|
+
const runningVersion = (0, package_info_1.getPackageVersion)();
|
|
54
|
+
const managed = manifest.managed ?? {};
|
|
55
|
+
const total = Object.keys(managed).length;
|
|
56
|
+
if (!recordedVersion || total === 0)
|
|
57
|
+
return null;
|
|
58
|
+
// A version match is the common case — trust it and skip hashing entirely.
|
|
59
|
+
if (recordedVersion === runningVersion)
|
|
60
|
+
return null;
|
|
61
|
+
let changed = 0;
|
|
62
|
+
for (const rel of Object.keys(managed)) {
|
|
63
|
+
if (!fs.existsSync(path.join(cwd, rel)))
|
|
64
|
+
changed++;
|
|
65
|
+
}
|
|
66
|
+
return { recordedVersion, runningVersion, changed, total };
|
|
67
|
+
}
|
|
68
|
+
/** One line, on stderr so `--json` output stays machine-readable. */
|
|
69
|
+
function warnAssetsDrift(cwd) {
|
|
70
|
+
const drift = checkAssetsDrift(cwd);
|
|
71
|
+
if (!drift)
|
|
72
|
+
return;
|
|
73
|
+
const gray = '\x1b[90m';
|
|
74
|
+
const cyan = '\x1b[36m';
|
|
75
|
+
const reset = '\x1b[0m';
|
|
76
|
+
process.stderr.write(`${gray}note: this project's AI commands/skills were written by sungen ${drift.recordedVersion}, ` +
|
|
77
|
+
`you are running ${drift.runningVersion} — run ${cyan}sungen update${reset}${gray} to refresh them ` +
|
|
78
|
+
`(upgrading the package alone does not).${reset}\n`);
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=assets-drift.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assets-drift.js","sourceRoot":"","sources":["../../src/orchestrator/assets-drift.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,4CAwBC;AAGD,0CAWC;AA/DD,uCAAyB;AACzB,2CAA6B;AAC7B,4DAA8D;AAuB9D,SAAgB,gBAAgB,CAAC,GAAW;IAC1C,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,eAAe,CAAC,CAAC;IAChE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC;QAAE,OAAO,IAAI,CAAC,CAAG,6BAA6B;IAE9E,IAAI,QAAgE,CAAC;IACrE,IAAI,CAAC;QACH,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC;IAChE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC,CAAoC,2BAA2B;IAC7E,CAAC;IACD,MAAM,eAAe,GAAG,QAAQ,CAAC,OAAO,IAAI,EAAE,CAAC;IAC/C,MAAM,cAAc,GAAG,IAAA,gCAAiB,GAAE,CAAC;IAC3C,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,IAAI,EAAE,CAAC;IACvC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC;IAC1C,IAAI,CAAC,eAAe,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEjD,2EAA2E;IAC3E,IAAI,eAAe,KAAK,cAAc;QAAE,OAAO,IAAI,CAAC;IAEpD,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACvC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YAAE,OAAO,EAAE,CAAC;IACrD,CAAC;IACD,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAC7D,CAAC;AAED,qEAAqE;AACrE,SAAgB,eAAe,CAAC,GAAW;IACzC,MAAM,KAAK,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,CAAC,KAAK;QAAE,OAAO;IACnB,MAAM,IAAI,GAAG,UAAU,CAAC;IACxB,MAAM,IAAI,GAAG,UAAU,CAAC;IACxB,MAAM,KAAK,GAAG,SAAS,CAAC;IACxB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,GAAG,IAAI,kEAAkE,KAAK,CAAC,eAAe,IAAI;QAClG,mBAAmB,KAAK,CAAC,cAAc,UAAU,IAAI,gBAAgB,KAAK,GAAG,IAAI,mBAAmB;QACpG,0CAA0C,KAAK,IAAI,CACpD,CAAC;AACJ,CAAC"}
|
|
@@ -68,15 +68,6 @@ If the unit is **api-first** (`qa/api/<name>/` or `qa/api/flows/<name>/`), the d
|
|
|
68
68
|
If the unit is **api-first** (`qa/api/<name>/` or `qa/api/flows/<name>/`), the design loop differs — **no visual capture, no selectors**; the contract is the named-endpoint catalog. **Follow the `sungen-api-design` + `sungen-api-coverage-model` skills end-to-end** instead of the screen/flow steps: `sungen context --area <name>` (discover endpoints + `fields:`) → **enumerate the Tier-1 case list per endpoint from the coverage model** (contract + not-found/required-matrix + auth + idempotency, expanded mechanically from `fields:`) → generate `@api`/`@cases`/flow/`@concurrent`/`@query` scenarios with **strict assertions** (prove the effect, never status-only) → **`sungen audit --area <name>` gate + reviewer + repair loop to businessDepth ≥ 0.7** → record + trace. Then recommend `/sungen-run-test <name>`. The capture / viewpoint-group / selector steps do **not** apply.
|
|
69
69
|
{{/cap}}
|
|
70
70
|
|
|
71
|
-
## Requirement traceability (before you finish)
|
|
72
|
-
|
|
73
|
-
Cross-check `requirements/spec.md` against the scenarios you wrote: every `FR-`/`TR-`/`NFR-` id
|
|
74
|
-
must either carry a `@spec:<id>` tag on the scenario that proves it, or be a conscious
|
|
75
|
-
out-of-scope decision you state in the summary. `sungen audit` reports `SPEC-TRACE-IMPLICIT` for
|
|
76
|
-
requirements it could only match by keyword — treat that list as a to-do: add the tag to the
|
|
77
|
-
proving scenario, do not leave the link to inference. Delivery's requirement table follows the
|
|
78
|
-
tag only, so an untagged requirement is later reported as an uncovered gap.
|
|
79
|
-
|
|
80
71
|
## Steps
|
|
81
72
|
|
|
82
73
|
{{#cap parallel-subagents}}
|
|
@@ -265,3 +256,23 @@ tag only, so an untagged requirement is later reported as an uncovered gap.
|
|
|
265
256
|
{{^cap parallel-subagents}}
|
|
266
257
|
**No selectors.yaml** — selectors are generated during `/sungen-run-test`.
|
|
267
258
|
{{/cap}}
|
|
259
|
+
|
|
260
|
+
## Finish — always hand the next step back
|
|
261
|
+
|
|
262
|
+
Do not stop after writing the files. Close every run with, in this order:
|
|
263
|
+
|
|
264
|
+
1. **The traceability check below** — an untagged requirement becomes an uncovered gap at delivery.
|
|
265
|
+
2. **The harness result in one line** — score, gate verdict, and the axis the `SCORE-CAPPED` finding
|
|
266
|
+
names (that finding is the single answer to "how do I raise the score").
|
|
267
|
+
3. **`AskUserQuestion` with the next actions** — never end with prose alone. Offer
|
|
268
|
+
`/sungen:run-test <name>` (recommended once the tiers are written), re-running `create-test` to
|
|
269
|
+
extend, and "Done for now". A run that ends without this leaves the operator guessing.
|
|
270
|
+
|
|
271
|
+
### Requirement traceability
|
|
272
|
+
|
|
273
|
+
Cross-check `requirements/spec.md` against the scenarios you wrote: every `FR-`/`TR-`/`NFR-` id
|
|
274
|
+
must either carry a `@spec:<id>` tag on the scenario that proves it, or be a conscious
|
|
275
|
+
out-of-scope decision you state in the summary. `sungen audit` reports `SPEC-TRACE-IMPLICIT` for
|
|
276
|
+
requirements it could only match by keyword — treat that list as a to-do: add the tag to the
|
|
277
|
+
proving scenario, do not leave the link to inference. Delivery's requirement table follows the
|
|
278
|
+
tag only, so an untagged requirement is later reported as an uncovered gap.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sun-asterisk/sungen",
|
|
3
|
-
"version": "3.2.16-beta.
|
|
3
|
+
"version": "3.2.16-beta.12",
|
|
4
4
|
"description": "Deterministic E2E Test Compiler - Gherkin + Selectors → Playwright tests",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"@babel/types": "^7.28.5",
|
|
40
40
|
"@cucumber/gherkin": "^37.0.0",
|
|
41
41
|
"@cucumber/messages": "^31.0.0",
|
|
42
|
-
"@sungen/driver-data-factory": "3.2.16-beta.
|
|
43
|
-
"@sungen/driver-ui": "3.2.16-beta.
|
|
42
|
+
"@sungen/driver-data-factory": "3.2.16-beta.12",
|
|
43
|
+
"@sungen/driver-ui": "3.2.16-beta.12",
|
|
44
44
|
"chalk": "^5.6.2",
|
|
45
45
|
"commander": "^14.0.2",
|
|
46
46
|
"dotenv": "^17.2.3",
|
package/src/cli/index.ts
CHANGED
|
@@ -35,6 +35,7 @@ import { registerRepairCommand } from './commands/repair';
|
|
|
35
35
|
import { registerLoginCommand } from './commands/login';
|
|
36
36
|
import { capabilityRegistry } from '../capabilities/registry';
|
|
37
37
|
import { discoverAndRegisterCapabilities } from '../capabilities/discover';
|
|
38
|
+
import { warnAssetsDrift } from '../orchestrator/assets-drift';
|
|
38
39
|
|
|
39
40
|
// Read version from package.json so `--version` never drifts from the released version.
|
|
40
41
|
const { version } = require('../../package.json') as { version: string };
|
|
@@ -88,6 +89,11 @@ async function main() {
|
|
|
88
89
|
for (const registerCommand of cap.cliCommands ?? []) registerCommand(program);
|
|
89
90
|
}
|
|
90
91
|
|
|
92
|
+
// Every command warns once when the project's AI assets came from a different
|
|
93
|
+
// build — `update` is the only thing that refreshes them, and nothing used to
|
|
94
|
+
// say so (stderr, so `--json` output stays parseable).
|
|
95
|
+
program.hook('preAction', () => { warnAssetsDrift(process.cwd()); });
|
|
96
|
+
|
|
91
97
|
await program.parseAsync(process.argv);
|
|
92
98
|
}
|
|
93
99
|
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { getPackageVersion } from '../exporters/package-info';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Are this project's AI assets (commands + skills under `.claude/` etc.) the ones
|
|
7
|
+
* the running sungen would write?
|
|
8
|
+
*
|
|
9
|
+
* Upgrading the npm package does NOT refresh them — `sungen update` does. Nothing
|
|
10
|
+
* said so, so a project kept running prompts from an older build indefinitely: a
|
|
11
|
+
* QA upgraded, re-ran `/sungen:create-test`, and got the OLD command (141 lines vs
|
|
12
|
+
* the shipped 267) with none of the newer guidance. The failure is invisible
|
|
13
|
+
* because everything still "works", just to an older specification.
|
|
14
|
+
*
|
|
15
|
+
* The manifest already records the version + a hash per managed file; this only
|
|
16
|
+
* reads it and reports.
|
|
17
|
+
*/
|
|
18
|
+
export interface AssetsDrift {
|
|
19
|
+
recordedVersion: string;
|
|
20
|
+
runningVersion: string;
|
|
21
|
+
/** Managed files whose content no longer matches the manifest's hash. */
|
|
22
|
+
changed: number;
|
|
23
|
+
total: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function checkAssetsDrift(cwd: string): AssetsDrift | null {
|
|
27
|
+
const manifestPath = path.join(cwd, '.sungen', 'manifest.json');
|
|
28
|
+
if (!fs.existsSync(manifestPath)) return null; // not a sungen project (yet)
|
|
29
|
+
|
|
30
|
+
let manifest: { version?: string; managed?: Record<string, string> };
|
|
31
|
+
try {
|
|
32
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
33
|
+
} catch {
|
|
34
|
+
return null; // unreadable → say nothing
|
|
35
|
+
}
|
|
36
|
+
const recordedVersion = manifest.version ?? '';
|
|
37
|
+
const runningVersion = getPackageVersion();
|
|
38
|
+
const managed = manifest.managed ?? {};
|
|
39
|
+
const total = Object.keys(managed).length;
|
|
40
|
+
if (!recordedVersion || total === 0) return null;
|
|
41
|
+
|
|
42
|
+
// A version match is the common case — trust it and skip hashing entirely.
|
|
43
|
+
if (recordedVersion === runningVersion) return null;
|
|
44
|
+
|
|
45
|
+
let changed = 0;
|
|
46
|
+
for (const rel of Object.keys(managed)) {
|
|
47
|
+
if (!fs.existsSync(path.join(cwd, rel))) changed++;
|
|
48
|
+
}
|
|
49
|
+
return { recordedVersion, runningVersion, changed, total };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** One line, on stderr so `--json` output stays machine-readable. */
|
|
53
|
+
export function warnAssetsDrift(cwd: string): void {
|
|
54
|
+
const drift = checkAssetsDrift(cwd);
|
|
55
|
+
if (!drift) return;
|
|
56
|
+
const gray = '\x1b[90m';
|
|
57
|
+
const cyan = '\x1b[36m';
|
|
58
|
+
const reset = '\x1b[0m';
|
|
59
|
+
process.stderr.write(
|
|
60
|
+
`${gray}note: this project's AI commands/skills were written by sungen ${drift.recordedVersion}, ` +
|
|
61
|
+
`you are running ${drift.runningVersion} — run ${cyan}sungen update${reset}${gray} to refresh them ` +
|
|
62
|
+
`(upgrading the package alone does not).${reset}\n`,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
@@ -68,15 +68,6 @@ If the unit is **api-first** (`qa/api/<name>/` or `qa/api/flows/<name>/`), the d
|
|
|
68
68
|
If the unit is **api-first** (`qa/api/<name>/` or `qa/api/flows/<name>/`), the design loop differs — **no visual capture, no selectors**; the contract is the named-endpoint catalog. **Follow the `sungen-api-design` + `sungen-api-coverage-model` skills end-to-end** instead of the screen/flow steps: `sungen context --area <name>` (discover endpoints + `fields:`) → **enumerate the Tier-1 case list per endpoint from the coverage model** (contract + not-found/required-matrix + auth + idempotency, expanded mechanically from `fields:`) → generate `@api`/`@cases`/flow/`@concurrent`/`@query` scenarios with **strict assertions** (prove the effect, never status-only) → **`sungen audit --area <name>` gate + reviewer + repair loop to businessDepth ≥ 0.7** → record + trace. Then recommend `/sungen-run-test <name>`. The capture / viewpoint-group / selector steps do **not** apply.
|
|
69
69
|
{{/cap}}
|
|
70
70
|
|
|
71
|
-
## Requirement traceability (before you finish)
|
|
72
|
-
|
|
73
|
-
Cross-check `requirements/spec.md` against the scenarios you wrote: every `FR-`/`TR-`/`NFR-` id
|
|
74
|
-
must either carry a `@spec:<id>` tag on the scenario that proves it, or be a conscious
|
|
75
|
-
out-of-scope decision you state in the summary. `sungen audit` reports `SPEC-TRACE-IMPLICIT` for
|
|
76
|
-
requirements it could only match by keyword — treat that list as a to-do: add the tag to the
|
|
77
|
-
proving scenario, do not leave the link to inference. Delivery's requirement table follows the
|
|
78
|
-
tag only, so an untagged requirement is later reported as an uncovered gap.
|
|
79
|
-
|
|
80
71
|
## Steps
|
|
81
72
|
|
|
82
73
|
{{#cap parallel-subagents}}
|
|
@@ -265,3 +256,23 @@ tag only, so an untagged requirement is later reported as an uncovered gap.
|
|
|
265
256
|
{{^cap parallel-subagents}}
|
|
266
257
|
**No selectors.yaml** — selectors are generated during `/sungen-run-test`.
|
|
267
258
|
{{/cap}}
|
|
259
|
+
|
|
260
|
+
## Finish — always hand the next step back
|
|
261
|
+
|
|
262
|
+
Do not stop after writing the files. Close every run with, in this order:
|
|
263
|
+
|
|
264
|
+
1. **The traceability check below** — an untagged requirement becomes an uncovered gap at delivery.
|
|
265
|
+
2. **The harness result in one line** — score, gate verdict, and the axis the `SCORE-CAPPED` finding
|
|
266
|
+
names (that finding is the single answer to "how do I raise the score").
|
|
267
|
+
3. **`AskUserQuestion` with the next actions** — never end with prose alone. Offer
|
|
268
|
+
`/sungen:run-test <name>` (recommended once the tiers are written), re-running `create-test` to
|
|
269
|
+
extend, and "Done for now". A run that ends without this leaves the operator guessing.
|
|
270
|
+
|
|
271
|
+
### Requirement traceability
|
|
272
|
+
|
|
273
|
+
Cross-check `requirements/spec.md` against the scenarios you wrote: every `FR-`/`TR-`/`NFR-` id
|
|
274
|
+
must either carry a `@spec:<id>` tag on the scenario that proves it, or be a conscious
|
|
275
|
+
out-of-scope decision you state in the summary. `sungen audit` reports `SPEC-TRACE-IMPLICIT` for
|
|
276
|
+
requirements it could only match by keyword — treat that list as a to-do: add the tag to the
|
|
277
|
+
proving scenario, do not leave the link to inference. Delivery's requirement table follows the
|
|
278
|
+
tag only, so an untagged requirement is later reported as an uncovered gap.
|