ccqa 1.58.1 → 1.58.3
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/bin/ccqa.mjs +36 -23
- package/dist/hub-client/index.d.cts +2 -2
- package/dist/hub-client/index.d.mts +2 -2
- package/dist/package.json +1 -1
- package/package.json +1 -1
package/dist/bin/ccqa.mjs
CHANGED
|
@@ -7868,7 +7868,7 @@ function reuseFirstContract(hasDraft, draftInvariant) {
|
|
|
7868
7868
|
"3. **Match the examples.** Write in the same style as the conventions examples: naming, structure, assertion phrasing, import layout. Where you follow an existing file rather than the plain reading of a guideline — a waiting pattern, a locator shape, a teardown structure — say which file, by path, in the comment. \"Following the existing implementation\" without naming it cannot be checked by the next reader, and it is how a pattern that was right once outlives the reason for it."
|
|
7869
7869
|
];
|
|
7870
7870
|
if (hasDraft) {
|
|
7871
|
-
rules.push("4. **The draft is ground truth.**
|
|
7871
|
+
rules.push("4. **The draft is ground truth for the route, not for the form of its checks.** Keep its operation sequence: no reordering or dropping of the actions the case needs. Its `expect` lines are what the recorder observed on the way, written in one fixed form (`await expect(...)`, one per observation). Which of them the test keeps, and in which form — hard or soft, where the check lives, how the locator is scoped — is decided by the case's expected results and the project's conventions, never by the draft. Where the conventions and the draft disagree, follow the conventions. What an expected result claims must still be checked: never loosen the value it states.");
|
|
7872
7872
|
if (draftInvariant) rules.push(`5. ${draftInvariant}`);
|
|
7873
7873
|
}
|
|
7874
7874
|
return `## Reuse contract\n\n${rules.join("\n")}`;
|
|
@@ -7916,6 +7916,8 @@ function buildLlmGenPrompt(input) {
|
|
|
7916
7916
|
sections.push(input.taskInstructions);
|
|
7917
7917
|
const steps = input.steps.map(formatStep).join("\n");
|
|
7918
7918
|
sections.push(`## Test spec\n\nTitle: ${input.specTitle}\n\nSteps:\n${steps}`);
|
|
7919
|
+
const facts = caseFacts(input);
|
|
7920
|
+
if (facts) sections.push(facts);
|
|
7919
7921
|
if (input.draft) sections.push(`## Mechanical draft (recorded ground truth)\n\nPath: ${input.draft.path}\n\n\`\`\`
|
|
7920
7922
|
` + input.draft.contents + "\n```");
|
|
7921
7923
|
if (input.resources.length > 0) {
|
|
@@ -7936,6 +7938,19 @@ These are the project's rules for the code you write, not background reading. Co
|
|
|
7936
7938
|
return sections.join("\n\n") + languageDirective(input.language);
|
|
7937
7939
|
}
|
|
7938
7940
|
/**
|
|
7941
|
+
* What the case states beyond its steps. Project conventions key rules on
|
|
7942
|
+
* these (which checks are the case's, where it may run), and the review that
|
|
7943
|
+
* follows already reads them — a rewrite without them can only guess.
|
|
7944
|
+
*/
|
|
7945
|
+
function caseFacts(input) {
|
|
7946
|
+
const parts = [];
|
|
7947
|
+
if (input.expectations && input.expectations.length > 0) parts.push(`### Expected results\n\nThe case states these for the flow as a whole. They are the checks the test exists for:\n\n${input.expectations.map((e) => `- ${body(e)}`).join("\n")}`);
|
|
7948
|
+
if (input.cleanup && input.cleanup.length > 0) parts.push(`### Cleanup steps\n\n${input.cleanup.map(formatStep).join("\n")}`);
|
|
7949
|
+
if (input.cleanupExpectations && input.cleanupExpectations.length > 0) parts.push(`### Expected results of the cleanup\n\n${input.cleanupExpectations.map((e) => `- ${body(e)}`).join("\n")}`);
|
|
7950
|
+
for (const c of input.context ?? []) parts.push(`### ${c.heading}\n\n${c.body.trim()}`);
|
|
7951
|
+
return parts.length === 0 ? "" : `## About this case\n\n${parts.join("\n\n")}`;
|
|
7952
|
+
}
|
|
7953
|
+
/**
|
|
7939
7954
|
* Prompt for one iteration of the verification loop: the generated files
|
|
7940
7955
|
* failed their run command; ask for corrected files under the same contract.
|
|
7941
7956
|
*/
|
|
@@ -8304,7 +8319,7 @@ function weakerTwin(file, source) {
|
|
|
8304
8319
|
function decidesNothing(input) {
|
|
8305
8320
|
const source = input.files.get(input.testPath);
|
|
8306
8321
|
if (source === void 0 || input.caseText.join("").trim().length === 0) return [];
|
|
8307
|
-
if (/\bexpect
|
|
8322
|
+
if (/\bexpect(?:\.soft)?\(|\bjudgeByLlm\b/.test(source)) return [];
|
|
8308
8323
|
return [{
|
|
8309
8324
|
file: input.testPath,
|
|
8310
8325
|
line: 1,
|
|
@@ -8373,7 +8388,7 @@ function containerOfPageText(file, source) {
|
|
|
8373
8388
|
*/
|
|
8374
8389
|
function explained(all, i) {
|
|
8375
8390
|
let above = i - 1;
|
|
8376
|
-
while (above >= 0 && /\bexpect
|
|
8391
|
+
while (above >= 0 && /\bexpect(?:\.soft)?\(|^\s*$|^\s*\)/.test(all[above] ?? "")) above -= 1;
|
|
8377
8392
|
return /^\s*\/\//.test(all[above] ?? "");
|
|
8378
8393
|
}
|
|
8379
8394
|
/**
|
|
@@ -8388,7 +8403,7 @@ function explained(all, i) {
|
|
|
8388
8403
|
function unjustifiedFirst(file, source) {
|
|
8389
8404
|
const all = lines(source);
|
|
8390
8405
|
return all.flatMap((text, i) => {
|
|
8391
|
-
if (!/\bexpect
|
|
8406
|
+
if (!/\bexpect(?:\.soft)?\(/.test(text) || !/\.first\(\)/.test(text)) return [];
|
|
8392
8407
|
if (explained(all, i)) return [];
|
|
8393
8408
|
return [{
|
|
8394
8409
|
file,
|
|
@@ -8463,7 +8478,7 @@ function assertedStrings(text, locators) {
|
|
|
8463
8478
|
function unaskedAssertions(file, source, locators, said) {
|
|
8464
8479
|
const all = lines(source);
|
|
8465
8480
|
return all.flatMap((text, i) => {
|
|
8466
|
-
if (!/\bexpect
|
|
8481
|
+
if (!/\bexpect(?:\.soft)?\(/.test(text)) return [];
|
|
8467
8482
|
const strings = assertedStrings(text, locators);
|
|
8468
8483
|
if (strings.length === 0 || strings.some((s) => said.includes(s))) return [];
|
|
8469
8484
|
if (explained(all, i)) return [];
|
|
@@ -8710,16 +8725,14 @@ function rulesReview(guides) {
|
|
|
8710
8725
|
""
|
|
8711
8726
|
])
|
|
8712
8727
|
],
|
|
8713
|
-
answerShape: "{ \"findings\": [ { \"stepId\": \"step-05\", \"problem\": \"…\" } ], \"ruleViolations\": [ { \"file\": \"…\", \"guide\": \"…\", \"rule\": \"…\", \"code\": \"…\"
|
|
8728
|
+
answerShape: "{ \"findings\": [ { \"stepId\": \"step-05\", \"problem\": \"…\" } ], \"ruleViolations\": [ { \"file\": \"…\", \"guide\": \"…\", \"rule\": \"…\", \"code\": \"…\" } ] }",
|
|
8714
8729
|
answerNote: [
|
|
8715
8730
|
"",
|
|
8716
8731
|
"In `ruleViolations`: `file` is the file's path in this repository, and `code`",
|
|
8717
8732
|
"the lines that break the rule. `guide` and `rule` are where the rule comes",
|
|
8718
8733
|
"from and what it says — the document's path and its own words when you can",
|
|
8719
8734
|
"quote one; otherwise the path of a file you read that shows the convention,",
|
|
8720
|
-
"and what you counted (\"N of M files under <dir> do X\").
|
|
8721
|
-
"\"blocking\" when you would hold the change until it is fixed, and \"advisory\"",
|
|
8722
|
-
"when it is worth saying but you would approve anyway. An empty",
|
|
8735
|
+
"and what you counted (\"N of M files under <dir> do X\"). An empty",
|
|
8723
8736
|
"`ruleViolations` means every file follows every rule you could quote or",
|
|
8724
8737
|
"count — write the key either way."
|
|
8725
8738
|
]
|
|
@@ -9150,8 +9163,7 @@ const ViolationSchema = z.object({
|
|
|
9150
9163
|
file: z.string(),
|
|
9151
9164
|
guide: z.string(),
|
|
9152
9165
|
rule: z.string(),
|
|
9153
|
-
code: z.string()
|
|
9154
|
-
severity: z.enum(["blocking", "advisory"]).catch("blocking")
|
|
9166
|
+
code: z.string()
|
|
9155
9167
|
});
|
|
9156
9168
|
const ViolationsSchema = z.object({ ruleViolations: z.array(z.unknown()) });
|
|
9157
9169
|
/**
|
|
@@ -9239,14 +9251,11 @@ function formatFinding(finding, labels = evidenceLabels()) {
|
|
|
9239
9251
|
}
|
|
9240
9252
|
/**
|
|
9241
9253
|
* The warning a rule violation becomes: the file, the rule in the words it was
|
|
9242
|
-
* judged by, and where that came from.
|
|
9243
|
-
*
|
|
9244
|
-
* offending code is left out: it is what the fix pass needs, not what a reader
|
|
9245
|
-
* scanning a log does.
|
|
9254
|
+
* judged by, and where that came from. The offending code is left out: it is
|
|
9255
|
+
* what the fix pass needs, not what a reader scanning a log does.
|
|
9246
9256
|
*/
|
|
9247
9257
|
function formatViolation(violation) {
|
|
9248
|
-
|
|
9249
|
-
return `${violation.file}: ${violation.rule} (${violation.guide}${advisory})`;
|
|
9258
|
+
return `${violation.file}: ${violation.rule} (${violation.guide})`;
|
|
9250
9259
|
}
|
|
9251
9260
|
/**
|
|
9252
9261
|
* Hand the written files to a reviewer who knows nothing about where they came
|
|
@@ -14789,6 +14798,10 @@ async function generateWithLlmEngine(req) {
|
|
|
14789
14798
|
specTitle: ctx.spec.title,
|
|
14790
14799
|
steps: req.steps,
|
|
14791
14800
|
draft: req.draft,
|
|
14801
|
+
expectations: ctx.expectations,
|
|
14802
|
+
cleanup: ctx.cleanup,
|
|
14803
|
+
cleanupExpectations: ctx.cleanupExpectations,
|
|
14804
|
+
context: ctx.context ?? [],
|
|
14792
14805
|
...req.draftInvariant ? { draftInvariant: req.draftInvariant } : {},
|
|
14793
14806
|
resources: resources.map(toPromptResource),
|
|
14794
14807
|
conventionSections: conventions.sections,
|
|
@@ -15179,13 +15192,12 @@ function uncheckedSteps(review, alreadyAsked = /* @__PURE__ */ new Set()) {
|
|
|
15179
15192
|
* arrived. Absent violations are absent either way — there is nothing here to
|
|
15180
15193
|
* act on when that half never came.
|
|
15181
15194
|
*
|
|
15182
|
-
*
|
|
15183
|
-
*
|
|
15184
|
-
*
|
|
15185
|
-
* the advisory ones too, marked as such.
|
|
15195
|
+
* Every violation the reviewer reports: a rule the project wrote down, or a
|
|
15196
|
+
* convention its files share, is the project's decision, not the reviewer's to
|
|
15197
|
+
* waive.
|
|
15186
15198
|
*/
|
|
15187
15199
|
function guideViolations(review, alreadyAsked = /* @__PURE__ */ new Set()) {
|
|
15188
|
-
const violations = (review?.ruleViolations ?? []).filter((v) =>
|
|
15200
|
+
const violations = (review?.ruleViolations ?? []).filter((v) => !alreadyAsked.has(violationKey(v)));
|
|
15189
15201
|
if (violations.length === 0) return null;
|
|
15190
15202
|
return {
|
|
15191
15203
|
exitCode: 1,
|
|
@@ -15474,7 +15486,7 @@ function headerPreserveRule(header, titleSuffix) {
|
|
|
15474
15486
|
* rejection, which costs a whole round.
|
|
15475
15487
|
*/
|
|
15476
15488
|
function stepCommentPreserveRule() {
|
|
15477
|
-
return "**Keep every `test.step(...)` block, with its title exactly as the draft wrote it.** Those titles are how the evidence table, the run's per-step screenshots and the review of this test say which assertions belong to which step. A reshaped one parses as no step at all, and every step then reads as deciding nothing. Keep the wording, the numbering and the punctuation, and keep each step's actions inside its own block — a step's call to a page-object method belongs in the block, not outside it. Do not merge, split or reorder the blocks. A test already at this path may have been written by an older ccqa in a different shape: the draft
|
|
15489
|
+
return "**Keep every `test.step(...)` block, with its title exactly as the draft wrote it.** Those titles are how the evidence table, the run's per-step screenshots and the review of this test say which assertions belong to which step. A reshaped one parses as no step at all, and every step then reads as deciding nothing. Keep the wording, the numbering and the punctuation, and keep each step's actions inside its own block — a step's call to a page-object method belongs in the block, not outside it. Do not merge, split or reorder the blocks. A test already at this path may have been written by an older ccqa in a different shape: the draft's step blocks are what this file must keep, not that one's.";
|
|
15478
15490
|
}
|
|
15479
15491
|
/**
|
|
15480
15492
|
* Told to the rewrite, because the alternative failure is silent: a claim
|
|
@@ -26488,6 +26500,7 @@ async function runGenerateLocked(resolved, opts, cwd) {
|
|
|
26488
26500
|
cleanup: testCase.cleanup,
|
|
26489
26501
|
expectations: testCase.expectations,
|
|
26490
26502
|
cleanupExpectations: testCase.cleanupExpectations,
|
|
26503
|
+
...testCase.context.length > 0 ? { context: testCase.context } : {},
|
|
26491
26504
|
fields: testCase.fields,
|
|
26492
26505
|
cwd,
|
|
26493
26506
|
testPath,
|
|
@@ -35,9 +35,9 @@ declare const RunSchema: z.ZodObject<{
|
|
|
35
35
|
running: "running";
|
|
36
36
|
}>;
|
|
37
37
|
kind: z.ZodDefault<z.ZodEnum<{
|
|
38
|
-
record: "record";
|
|
39
38
|
run: "run";
|
|
40
39
|
drift: "drift";
|
|
40
|
+
record: "record";
|
|
41
41
|
}>>;
|
|
42
42
|
drift: z.ZodDefault<z.ZodNullable<z.ZodObject<{
|
|
43
43
|
specs: z.ZodNumber;
|
|
@@ -831,9 +831,9 @@ type ReportSpecResult = z.infer<typeof ReportSpecResultSchema>;
|
|
|
831
831
|
declare const RunReportDataSchema: z.ZodObject<{
|
|
832
832
|
schemaVersion: z.ZodLiteral<1>;
|
|
833
833
|
kind: z.ZodDefault<z.ZodEnum<{
|
|
834
|
-
record: "record";
|
|
835
834
|
run: "run";
|
|
836
835
|
drift: "drift";
|
|
836
|
+
record: "record";
|
|
837
837
|
}>>;
|
|
838
838
|
createdAt: z.ZodString;
|
|
839
839
|
runId: z.ZodNullable<z.ZodString>;
|
|
@@ -35,9 +35,9 @@ declare const RunSchema: z.ZodObject<{
|
|
|
35
35
|
running: "running";
|
|
36
36
|
}>;
|
|
37
37
|
kind: z.ZodDefault<z.ZodEnum<{
|
|
38
|
-
record: "record";
|
|
39
38
|
run: "run";
|
|
40
39
|
drift: "drift";
|
|
40
|
+
record: "record";
|
|
41
41
|
}>>;
|
|
42
42
|
drift: z.ZodDefault<z.ZodNullable<z.ZodObject<{
|
|
43
43
|
specs: z.ZodNumber;
|
|
@@ -831,9 +831,9 @@ type ReportSpecResult = z.infer<typeof ReportSpecResultSchema>;
|
|
|
831
831
|
declare const RunReportDataSchema: z.ZodObject<{
|
|
832
832
|
schemaVersion: z.ZodLiteral<1>;
|
|
833
833
|
kind: z.ZodDefault<z.ZodEnum<{
|
|
834
|
-
record: "record";
|
|
835
834
|
run: "run";
|
|
836
835
|
drift: "drift";
|
|
836
|
+
record: "record";
|
|
837
837
|
}>>;
|
|
838
838
|
createdAt: z.ZodString;
|
|
839
839
|
runId: z.ZodNullable<z.ZodString>;
|
package/dist/package.json
CHANGED