qfai 1.2.14 → 1.3.0

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.
@@ -14,6 +14,22 @@ This file defines the canonical stages and delegation expectations.
14
14
 
15
15
  ---
16
16
 
17
+ ## Change Type (Mandatory)
18
+
19
+ At the start of any work, classify the change and record it in:
20
+
21
+ - `delta.md` Change Log (latest CL entry)
22
+ - PR description (Change Type section)
23
+
24
+ Allowed values:
25
+
26
+ - Primary: `Initial | Behavior | Structural | Ops`
27
+ - Tags (optional): `@ui @api @db @nfr @docs @test`
28
+
29
+ Do not proceed without a declared Change Type.
30
+
31
+ ---
32
+
17
33
  ## Stages (canonical)
18
34
 
19
35
  0. Steering refresh (project memory bootstrap)
@@ -51,6 +51,18 @@ When unsure, read inputs in this order:
51
51
  - P3: `.qfai/specs/<spec-id>/delta.md` (Decision Records; if no spec yet, state "not applicable")
52
52
  - P4: other artifacts (spec.md, scenario.feature, contracts, evidence)
53
53
 
54
+ ## Change Type (Mandatory)
55
+
56
+ Before editing `delta.md`, declare the Change Type and record it in:
57
+
58
+ - `delta.md` Change Log (latest CL entry)
59
+ - PR description (Change Type section)
60
+
61
+ Allowed values:
62
+
63
+ - Primary: `Initial | Behavior | Structural | Ops`
64
+ - Tags (optional): `@ui @api @db @nfr @docs @test`
65
+
54
66
  ## Delta Rejected Guard (Mandatory)
55
67
 
56
68
  - Do NOT reintroduce options marked as rejected in delta.md.
@@ -38,6 +38,18 @@ When unsure, read inputs in this order:
38
38
  - P3: `.qfai/specs/<spec-id>/delta.md` (Decision Records; if no spec yet, state "not applicable")
39
39
  - P4: other artifacts (spec.md, scenario.feature, contracts, evidence)
40
40
 
41
+ ## Change Type (Mandatory)
42
+
43
+ Before updating `delta.md`, declare the Change Type and record it in:
44
+
45
+ - `delta.md` Change Log (latest CL entry)
46
+ - PR description (Change Type section)
47
+
48
+ Allowed values:
49
+
50
+ - Primary: `Initial | Behavior | Structural | Ops`
51
+ - Tags (optional): `@ui @api @db @nfr @docs @test`
52
+
41
53
  ## Delta Rejected Guard (Mandatory)
42
54
 
43
55
  - Do NOT reintroduce options marked as rejected in delta.md.
@@ -38,6 +38,18 @@ When unsure, read inputs in this order:
38
38
  - P3: `.qfai/specs/<spec-id>/delta.md` (Decision Records; if no spec yet, state "not applicable")
39
39
  - P4: other artifacts (spec.md, scenario.feature, contracts, evidence)
40
40
 
41
+ ## Change Type (Mandatory)
42
+
43
+ Before updating `delta.md`, declare the Change Type and record it in:
44
+
45
+ - `delta.md` Change Log (latest CL entry)
46
+ - PR description (Change Type section)
47
+
48
+ Allowed values:
49
+
50
+ - Primary: `Initial | Behavior | Structural | Ops`
51
+ - Tags (optional): `@ui @api @db @nfr @docs @test`
52
+
41
53
  ## Delta Rejected Guard (Mandatory)
42
54
 
43
55
  - Do NOT reintroduce options marked as rejected in delta.md.
@@ -38,6 +38,18 @@ When unsure, read inputs in this order:
38
38
  - P3: `.qfai/specs/<spec-id>/delta.md` (Decision Records; if no spec yet, state "not applicable")
39
39
  - P4: other artifacts (spec.md, scenario.feature, contracts, evidence)
40
40
 
41
+ ## Change Type (Mandatory)
42
+
43
+ Before updating `delta.md`, declare the Change Type and record it in:
44
+
45
+ - `delta.md` Change Log (latest CL entry)
46
+ - PR description (Change Type section)
47
+
48
+ Allowed values:
49
+
50
+ - Primary: `Initial | Behavior | Structural | Ops`
51
+ - Tags (optional): `@ui @api @db @nfr @docs @test`
52
+
41
53
  ## Delta Rejected Guard (Mandatory)
42
54
 
43
55
  - Do NOT reintroduce options marked as rejected in delta.md.
@@ -185,6 +185,9 @@ specs/
185
185
 
186
186
  - date: <YYYY-MM-DD>
187
187
  - author: <AI/role or human>
188
+ - change_type_primary: Initial | Behavior | Structural | Ops
189
+ - change_type_tags: <space-separated tags or empty>
190
+ - example: @api @db
188
191
  - scope: <files/areas>
189
192
  - change: <what changed>
190
193
  - reason: <why it changed>
@@ -204,6 +207,8 @@ specs/
204
207
  - selected: <option>
205
208
  - rejected:
206
209
  - <option> — <reason>
210
+ - do_not: <what must not be reintroduced>
211
+ - temptation: <why it may be tempting to reintroduce>
207
212
  - impact: <downstream impact>
208
213
  - followups: <todos>
209
214
  - related_contracts: <QFAI-CONTRACT-REF or IDs>
@@ -1184,8 +1184,8 @@ var import_promises7 = require("fs/promises");
1184
1184
  var import_node_path9 = __toESM(require("path"), 1);
1185
1185
  var import_node_url2 = require("url");
1186
1186
  async function resolveToolVersion() {
1187
- if ("1.2.14".length > 0) {
1188
- return "1.2.14";
1187
+ if ("1.3.0".length > 0) {
1188
+ return "1.3.0";
1189
1189
  }
1190
1190
  try {
1191
1191
  const packagePath = resolvePackageJsonPath();
@@ -3568,6 +3568,24 @@ async function validateCaseCatalogues(root, config) {
3568
3568
  // src/core/validators/delta.ts
3569
3569
  var import_promises15 = require("fs/promises");
3570
3570
  var import_node_path19 = __toESM(require("path"), 1);
3571
+ var CHANGE_TYPE_PRIMARY_RE = /^\s*[-*]?\s*change_type_primary\s*:\s*(.+)\s*$/im;
3572
+ var CHANGE_TYPE_TAGS_RE = /^\s*[-*]?\s*change_type_tags\s*:\s*(.*)\s*$/im;
3573
+ var DO_NOT_RE = /^\s*[-*]?\s*do_not\s*:/im;
3574
+ var TEMPTATION_RE = /^\s*[-*]?\s*temptation\s*:/im;
3575
+ var ALLOWED_CHANGE_TYPE_PRIMARY = /* @__PURE__ */ new Set([
3576
+ "initial",
3577
+ "behavior",
3578
+ "structural",
3579
+ "ops"
3580
+ ]);
3581
+ var ALLOWED_CHANGE_TYPE_TAGS = /* @__PURE__ */ new Set([
3582
+ "@ui",
3583
+ "@api",
3584
+ "@db",
3585
+ "@nfr",
3586
+ "@docs",
3587
+ "@test"
3588
+ ]);
3571
3589
  async function validateDeltas(root, config) {
3572
3590
  const specsRoot = resolvePath(root, config, "specsDir");
3573
3591
  const packs = await collectSpecPackDirs(specsRoot);
@@ -3629,10 +3647,8 @@ async function validateDeltas(root, config) {
3629
3647
  )
3630
3648
  );
3631
3649
  } else {
3632
- const hasRejected = /^\s*(?:[-*]\s*)?rejected\s*:/im.test(
3633
- decisionRecords.body
3634
- );
3635
- if (!hasRejected) {
3650
+ const rejectedBlocks = extractRejectedBlocks(decisionRecords.body);
3651
+ if (rejectedBlocks.length === 0) {
3636
3652
  issues.push(
3637
3653
  issue(
3638
3654
  "QFAI-DELTA-101",
@@ -3645,6 +3661,28 @@ async function validateDeltas(root, config) {
3645
3661
  "rejected \u3092\u6700\u4F4E1\u4EF6\u8A18\u8F09\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
3646
3662
  )
3647
3663
  );
3664
+ } else {
3665
+ const hasDoNot = rejectedBlocks.some((block) => DO_NOT_RE.test(block));
3666
+ const hasTemptation = rejectedBlocks.some(
3667
+ (block) => TEMPTATION_RE.test(block)
3668
+ );
3669
+ if (!hasDoNot || !hasTemptation) {
3670
+ const missing = [];
3671
+ if (!hasDoNot) missing.push("do_not");
3672
+ if (!hasTemptation) missing.push("temptation");
3673
+ issues.push(
3674
+ issue(
3675
+ "QFAI-DELTA-204",
3676
+ `Decision Records \u306B ${missing.join(" / ")} \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002`,
3677
+ "warning",
3678
+ deltaPath,
3679
+ "delta.rejectedDetails",
3680
+ void 0,
3681
+ "change",
3682
+ "rejected \u306B\u306F do_not / temptation \u3092\u6700\u4F4E1\u4EF6\u542B\u3081\u3066\u304F\u3060\u3055\u3044\u3002"
3683
+ )
3684
+ );
3685
+ }
3648
3686
  }
3649
3687
  }
3650
3688
  if (changeLog && decisionRecords) {
@@ -3663,6 +3701,54 @@ async function validateDeltas(root, config) {
3663
3701
  );
3664
3702
  }
3665
3703
  }
3704
+ if (changeLog) {
3705
+ const blocks = extractChangeLogBlocks(text, changeLog);
3706
+ for (const block of blocks) {
3707
+ const primary = extractChangeTypePrimary(block);
3708
+ if (!primary) {
3709
+ issues.push(
3710
+ issue(
3711
+ "QFAI-DELTA-201",
3712
+ "Change Log \u306E CL \u30D6\u30ED\u30C3\u30AF\u306B change_type_primary \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
3713
+ "warning",
3714
+ deltaPath,
3715
+ "delta.changeTypePrimary",
3716
+ void 0,
3717
+ "change",
3718
+ "change_type_primary \u3092\u8FFD\u52A0\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
3719
+ )
3720
+ );
3721
+ } else if (!isAllowedChangeTypePrimary(primary)) {
3722
+ issues.push(
3723
+ issue(
3724
+ "QFAI-DELTA-202",
3725
+ `change_type_primary \u304C\u4E0D\u6B63\u3067\u3059: ${primary}`,
3726
+ "warning",
3727
+ deltaPath,
3728
+ "delta.changeTypePrimary",
3729
+ void 0,
3730
+ "change",
3731
+ "change_type_primary \u306F Initial | Behavior | Structural | Ops \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
3732
+ )
3733
+ );
3734
+ }
3735
+ const invalidTags = extractInvalidChangeTypeTags(block);
3736
+ if (invalidTags && invalidTags.length > 0) {
3737
+ issues.push(
3738
+ issue(
3739
+ "QFAI-DELTA-203",
3740
+ `change_type_tags \u306B\u7121\u52B9\u306A\u30BF\u30B0\u304C\u3042\u308A\u307E\u3059: ${invalidTags.join(", ")}`,
3741
+ "warning",
3742
+ deltaPath,
3743
+ "delta.changeTypeTags",
3744
+ void 0,
3745
+ "change",
3746
+ "change_type_tags \u306F @ui @api @db @nfr @docs @test \u306E\u307F\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
3747
+ )
3748
+ );
3749
+ }
3750
+ }
3751
+ }
3666
3752
  }
3667
3753
  return issues;
3668
3754
  }
@@ -3675,6 +3761,98 @@ function findSection(sections, title) {
3675
3761
  }
3676
3762
  return null;
3677
3763
  }
3764
+ function extractChangeLogBlocks(text, changeLog) {
3765
+ const lines = text.split(/\r?\n/);
3766
+ const headings = parseHeadings(text).filter(
3767
+ (heading) => heading.level === 3 && heading.line >= changeLog.startLine && heading.line <= changeLog.endLine
3768
+ );
3769
+ if (headings.length === 0) {
3770
+ return [changeLog.body];
3771
+ }
3772
+ const blocks = [];
3773
+ for (let i = 0; i < headings.length; i += 1) {
3774
+ const current = headings[i];
3775
+ if (!current) continue;
3776
+ const next = headings[i + 1];
3777
+ const startLine = current.line + 1;
3778
+ const endLine = Math.min(
3779
+ changeLog.endLine,
3780
+ (next?.line ?? changeLog.endLine + 1) - 1
3781
+ );
3782
+ if (startLine > endLine) {
3783
+ blocks.push("");
3784
+ continue;
3785
+ }
3786
+ blocks.push(lines.slice(startLine - 1, endLine).join("\n"));
3787
+ }
3788
+ return blocks;
3789
+ }
3790
+ function extractChangeTypePrimary(block) {
3791
+ const match = block.match(CHANGE_TYPE_PRIMARY_RE);
3792
+ const value = match?.[1]?.trim() ?? "";
3793
+ return value.length > 0 ? value : null;
3794
+ }
3795
+ function extractRejectedBlocks(sectionBody) {
3796
+ const lines = sectionBody.split(/\r?\n/);
3797
+ const blocks = [];
3798
+ let currentIndent = null;
3799
+ let buffer = [];
3800
+ const flush = () => {
3801
+ if (currentIndent === null) {
3802
+ return;
3803
+ }
3804
+ blocks.push(buffer.join("\n"));
3805
+ buffer = [];
3806
+ currentIndent = null;
3807
+ };
3808
+ for (let i = 0; i < lines.length; i += 1) {
3809
+ const line = lines[i] ?? "";
3810
+ const match = line.match(/^(\s*)(?:[-*]\s*)?rejected\s*:(.*)$/i);
3811
+ if (match) {
3812
+ flush();
3813
+ const inlineValue = (match[2] ?? "").trim();
3814
+ if (inlineValue.length > 0) {
3815
+ blocks.push(inlineValue);
3816
+ currentIndent = null;
3817
+ } else {
3818
+ currentIndent = (match[1] ?? "").length;
3819
+ }
3820
+ continue;
3821
+ }
3822
+ if (currentIndent === null) {
3823
+ continue;
3824
+ }
3825
+ const indentMatch = line.match(/^(\s*)/);
3826
+ const indent = (indentMatch?.[1] ?? "").length;
3827
+ if (line.trim().length > 0 && indent <= currentIndent) {
3828
+ flush();
3829
+ i -= 1;
3830
+ continue;
3831
+ }
3832
+ buffer.push(line);
3833
+ }
3834
+ flush();
3835
+ return blocks;
3836
+ }
3837
+ function extractInvalidChangeTypeTags(block) {
3838
+ const match = block.match(CHANGE_TYPE_TAGS_RE);
3839
+ if (!match) {
3840
+ return null;
3841
+ }
3842
+ const raw = match[1]?.trim() ?? "";
3843
+ if (raw.length === 0) {
3844
+ return [];
3845
+ }
3846
+ const tags = raw.split(/[\s,]+/).map((tag) => tag.trim()).filter((tag) => tag.length > 0);
3847
+ const invalid = tags.filter((tag) => !isAllowedChangeTypeTag(tag));
3848
+ return invalid;
3849
+ }
3850
+ function isAllowedChangeTypePrimary(value) {
3851
+ return ALLOWED_CHANGE_TYPE_PRIMARY.has(value.trim().toLowerCase());
3852
+ }
3853
+ function isAllowedChangeTypeTag(value) {
3854
+ return ALLOWED_CHANGE_TYPE_TAGS.has(value.trim().toLowerCase());
3855
+ }
3678
3856
 
3679
3857
  // src/core/validators/ids.ts
3680
3858
  var import_promises16 = require("fs/promises");