@relipa/ai-flow-kit 0.2.0 → 0.2.2-beta.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.
Files changed (51) hide show
  1. package/README.md +4 -4
  2. package/custom/rules/java/spring-boot-rules.md +209 -0
  3. package/custom/rules/javascript/nestjs-examples.md +41 -0
  4. package/custom/rules/javascript/nestjs-rules.md +42 -0
  5. package/custom/rules/javascript/nodejs-express-examples.md +35 -0
  6. package/custom/rules/javascript/nodejs-express-rules.md +49 -0
  7. package/custom/rules/javascript/reactjs-examples.md +380 -0
  8. package/custom/rules/javascript/reactjs-rules.md +173 -0
  9. package/custom/rules/php/php-examples.md +161 -0
  10. package/custom/rules/php/php-rules.md +127 -0
  11. package/custom/rules/python/python-django-examples.md +34 -0
  12. package/custom/rules/python/python-django-rules.md +48 -0
  13. package/custom/rules/python/python-examples.md +32 -0
  14. package/custom/rules/python/python-fastapi-examples.md +30 -0
  15. package/custom/rules/python/python-fastapi-rules.md +35 -0
  16. package/custom/rules/python/python-ml-examples.md +187 -0
  17. package/custom/rules/python/python-ml-rules.md +121 -0
  18. package/custom/rules/python/python-rules.md +58 -0
  19. package/custom/skills/ba-skills/skill-ba-qna-template-v1.md +4 -4
  20. package/custom/skills/ba-skills/skill-ba-qna-v1.md +6 -0
  21. package/custom/skills/create-system-requirement/SKILL.md +99 -25
  22. package/custom/skills/create-system-requirement/system-requirement-template-v1.md +282 -0
  23. package/custom/skills/execute-flow/SKILL.md +142 -36
  24. package/custom/skills/execute-flow/templates/evidence-helper.ts +145 -0
  25. package/custom/skills/execute-flow/templates/playwright.config.ts +28 -8
  26. package/custom/skills/impact-analysis/SKILL.md +106 -106
  27. package/custom/skills/read-study-requirement/SKILL.md +1 -2
  28. package/custom/skills/report-customer/SKILL.md +99 -99
  29. package/custom/skills/script-sync/SKILL.md +54 -16
  30. package/custom/templates/nestjs.md +5 -72
  31. package/custom/templates/nodejs-express.md +5 -73
  32. package/custom/templates/php-plain.md +5 -261
  33. package/custom/templates/php.md +5 -261
  34. package/custom/templates/python-django.md +5 -71
  35. package/custom/templates/python-fastapi.md +5 -54
  36. package/custom/templates/python-ml.md +1 -269
  37. package/custom/templates/python.md +5 -79
  38. package/custom/templates/reactjs.md +5 -492
  39. package/custom/templates/shared/gate-workflow.md +18 -11
  40. package/custom/templates/shared/ml-gate-workflow.md +1 -0
  41. package/custom/templates/spring-boot.md +5 -224
  42. package/docs/common/CHANGELOG.md +24 -6
  43. package/docs/common/INDEX.md +1 -0
  44. package/docs/common/QUICK_START.md +1 -1
  45. package/docs/common/System-Requirement-Read-Guide.md +178 -0
  46. package/docs/common/Testing-Structure.md +31 -25
  47. package/docs/common/cli-reference.md +12 -10
  48. package/package.json +1 -1
  49. package/scripts/init.js +143 -40
  50. package/scripts/prompt.js +3 -3
  51. package/scripts/scaffold-playwright.js +2 -0
package/scripts/init.js CHANGED
@@ -25,14 +25,19 @@ function commandExists(cmd) {
25
25
 
26
26
  // Map framework → language family for picking the right rules
27
27
  const FRAMEWORK_LANGUAGE = {
28
- 'laravel': 'php',
29
- 'php': 'php',
30
- 'spring-boot': 'java',
31
- 'reactjs': 'javascript',
32
- 'nextjs': 'javascript',
33
- 'vue-nuxt': 'javascript',
34
- 'nestjs': 'javascript',
35
- 'python-ml': 'python',
28
+ 'laravel': 'php',
29
+ 'php': 'php',
30
+ 'php-plain': 'php',
31
+ 'spring-boot': 'java',
32
+ 'reactjs': 'javascript',
33
+ 'nextjs': 'javascript',
34
+ 'vue-nuxt': 'javascript',
35
+ 'nestjs': 'javascript',
36
+ 'nodejs-express': 'javascript',
37
+ 'python-ml': 'python',
38
+ 'python': 'python',
39
+ 'python-django': 'python',
40
+ 'python-fastapi': 'python',
36
41
  };
37
42
 
38
43
  // Frameworks that use the ML-specific gate workflow instead of the default one
@@ -69,8 +74,16 @@ const AI_TOOL_FILES = {
69
74
  // Codex is 'pointer' because it caps the combined AGENTS.md chain at
70
75
  // project_doc_max_bytes (32 KiB default) and silently drops whatever exceeds it.
71
76
  // The assembled workflow set is ~115 KB, so inlining would truncate it mid-gate.
77
+ //
78
+ // Claude is 'pointer' for a softer reason: no hard byte cap, but CLAUDE.md is
79
+ // loaded in full at the start of every session regardless of which gate (or none)
80
+ // is actually active, so inlining ~115 KB of DEV/TESTER/EXECUTE/gen-doc/ingest-data
81
+ // + BA + QA workflow text spends context on gates most sessions never touch.
82
+ // Claude Code already reads files on demand via Read/Skill, so a short pointer
83
+ // works the same way a Skill reference does.
72
84
  const AI_TOOL_LAYOUT = {
73
85
  'codex': 'pointer',
86
+ 'claude': 'pointer',
74
87
  };
75
88
 
76
89
  /** Return 'inline' or 'pointer' for a tool key. */
@@ -78,6 +91,39 @@ function resolveToolLayout(tool) {
78
91
  return AI_TOOL_LAYOUT[tool] || 'inline';
79
92
  }
80
93
 
94
+ // Skill-list rendering per tool.
95
+ // 'full' — verbose markdown table with description per skill (default, for
96
+ // tools with no native skill index — cursor/gemini/copilot/generic).
97
+ // 'compact' — bare skill-name list.
98
+ //
99
+ // Both Codex (.codex/skills/) and Claude Code (.claude/skills/) auto-discover
100
+ // name + description from each SKILL.md's frontmatter on their own, so restating
101
+ // the full table in the generated instruction file just burns context on every
102
+ // turn for information the tool already surfaces natively. The compact list still
103
+ // tells the AI which skill names exist and where to `Read` the full SKILL.md when
104
+ // a workflow step names one — it just stops duplicating the description.
105
+ const AI_TOOL_SKILL_LIST_STYLE = {
106
+ 'codex': 'compact',
107
+ 'claude': 'compact',
108
+ };
109
+
110
+ /** Return 'full' or 'compact' skill-list style for a tool key. */
111
+ function resolveSkillListStyle(tool) {
112
+ return AI_TOOL_SKILL_LIST_STYLE[tool] || 'full';
113
+ }
114
+
115
+ // Where each tool actually reads its skills from, for re-rooting paths in the
116
+ // generated registry/name-list. Only Codex mirrors skills into its own directory
117
+ // (.codex/skills/); every other tool reads the canonical .claude/skills/ directly.
118
+ const AI_TOOL_SKILLS_ROOT = {
119
+ 'codex': '.codex/skills',
120
+ };
121
+
122
+ /** Return the skills-root path to embed in a tool's generated instruction file. */
123
+ function resolveSkillsRoot(tool) {
124
+ return AI_TOOL_SKILLS_ROOT[tool] || '.claude/skills';
125
+ }
126
+
81
127
  // Shared workflow templates copied to .aiflow/instructions/ for pointer-layout tools.
82
128
  // Keys are the destination basenames; gate workflow resolves per framework.
83
129
  const WORKFLOW_INSTRUCTION_FILES = [
@@ -612,7 +658,8 @@ async function generateSkillNameList(projectDir, skillsRoot = '.codex/skills') {
612
658
  if (!names.length) return '';
613
659
 
614
660
  let out = `## Available skills\n\n`;
615
- out += `Codex indexes these automatically from \`${skillsRoot}/\`. When a workflow step says `;
661
+ out += `These skills are indexed automatically from \`${skillsRoot}/\` (name + description come from `;
662
+ out += `each SKILL.md's frontmatter). When a workflow step says `;
616
663
  out += `**INVOKE:** \`<name>\`, read \`${skillsRoot}/<name>/SKILL.md\` in full and follow it:\n\n`;
617
664
  out += names.sort().map(n => `\`${n}\``).join(' · ') + '\n';
618
665
  return out;
@@ -653,6 +700,29 @@ async function generateSkillRegistry(projectDir, skillsRoot = '.claude/skills')
653
700
  return registry;
654
701
  }
655
702
 
703
+ /**
704
+ * Write `finalContent` into `targetPath` under its own marker pair: replace the
705
+ * existing block in place if the markers are already present, append (separated
706
+ * by `sep`) if the file exists without them, or create the file fresh otherwise.
707
+ * Used for multi-framework projects, where several independent markers
708
+ * (one per framework, plus the shared tool/registry block) share one file.
709
+ */
710
+ async function upsertMarkerBlock(targetPath, markerStart, markerEnd, finalContent, sep) {
711
+ const exists = await fs.pathExists(targetPath);
712
+ if (!exists) {
713
+ await fs.writeFile(targetPath, finalContent);
714
+ return;
715
+ }
716
+ const existingContent = await fs.readFile(targetPath, 'utf-8');
717
+ const hasBlock = existingContent.includes(markerStart) && existingContent.includes(markerEnd);
718
+ if (hasBlock) {
719
+ const regex = new RegExp(`${markerStart}[\\s\\S]*?${markerEnd}`, 'g');
720
+ await fs.writeFile(targetPath, existingContent.replace(regex, finalContent));
721
+ } else {
722
+ await fs.appendFile(targetPath, sep + finalContent);
723
+ }
724
+ }
725
+
656
726
  async function setupFramework(projectDir, framework, multi = false, selectedTools = Object.keys(AI_TOOL_FILES), options = {}) {
657
727
  if (!framework) return;
658
728
  const frameworkTemplatePath = path.join(PKG_DIR, 'custom', 'templates', `${framework}.md`);
@@ -683,12 +753,19 @@ async function setupFramework(projectDir, framework, multi = false, selectedTool
683
753
  const workflowContent = [gateWorkflowContent, createSpecContent, createTestcaseContent].filter(Boolean).join('\n\n---\n\n');
684
754
 
685
755
  const separator = `\n\n---\n\n`;
686
- const skillRegistry = await generateSkillRegistry(projectDir);
687
- const codexSkillList = await generateSkillNameList(projectDir, '.codex/skills');
688
756
 
689
757
  const markerStart = '<!-- aiflow-kit-start -->';
690
758
  const markerEnd = '<!-- aiflow-kit-end -->';
691
759
 
760
+ // Multi-framework projects get exactly one shared block per tool (tool header +
761
+ // skill registry) instead of one copy per framework block — neither depends on
762
+ // which framework triggered this call, so repeating them per framework was pure
763
+ // duplication (e.g. spring-boot + php in the same project doubled the ~44-skill
764
+ // registry). Re-running for a second framework upserts this block in place via
765
+ // its fixed markers rather than appending another copy.
766
+ const sharedMarkerStart = '<!-- aiflow-kit-start:shared -->';
767
+ const sharedMarkerEnd = '<!-- aiflow-kit-end:shared -->';
768
+
692
769
  // Pointer-layout tools need the workflows on disk instead of inlined.
693
770
  const needsPointer = selectedTools.some(t => resolveToolLayout(t) === 'pointer');
694
771
  const instructionPaths = needsPointer
@@ -696,12 +773,13 @@ async function setupFramework(projectDir, framework, multi = false, selectedTool
696
773
  : [];
697
774
 
698
775
  /**
699
- * Assemble the marker block for one tool.
700
- * 'inline' tools get the whole workflow set embedded; 'pointer' tools get a
701
- * short "installed workflow files" list and read the workflows from disk.
776
+ * Tool header + skill registry + (for pointer tools) the "installed workflow
777
+ * files" pointer note none of this depends on which framework is active:
778
+ * the 3 instruction-file paths are always the same 3 filenames regardless of
779
+ * framework (only their on-disk *content* varies), so this used to be
780
+ * duplicated per framework block for no reason.
702
781
  */
703
- async function buildToolBlock(tool, blockMarkerStart, blockMarkerEnd) {
704
- const pointer = resolveToolLayout(tool) === 'pointer';
782
+ async function buildSharedBlock(tool) {
705
783
  let body = '';
706
784
 
707
785
  const toolTemplatePath = path.join(PKG_DIR, 'custom', 'templates', 'tools', `${tool}.md`);
@@ -709,19 +787,46 @@ async function setupFramework(projectDir, framework, multi = false, selectedTool
709
787
  body += await fs.readFile(toolTemplatePath, 'utf-8') + '\n\n';
710
788
  }
711
789
 
712
- if (frameworkContent) body += frameworkContent + '\n\n';
713
-
714
- const registry = pointer ? codexSkillList : skillRegistry;
790
+ const skillsRoot = resolveSkillsRoot(tool);
791
+ const registry = resolveSkillListStyle(tool) === 'compact'
792
+ ? await generateSkillNameList(projectDir, skillsRoot)
793
+ : await generateSkillRegistry(projectDir, skillsRoot);
715
794
  if (registry) body += registry + '\n\n';
716
795
 
717
- if (pointer) {
718
- if (instructionPaths.length) {
719
- body += `## Installed workflow files\n\n`;
720
- body += `Generated for framework \`${framework}\` by ai-flow-kit v${PKG_VERSION}. `;
721
- body += `Read the relevant file in full before starting a gate:\n\n`;
722
- body += instructionPaths.map(p => `- \`${p}\``).join('\n') + '\n\n';
723
- }
724
- } else if (workflowContent) {
796
+ const pointer = resolveToolLayout(tool) === 'pointer';
797
+ if (pointer && instructionPaths.length) {
798
+ body += `## Installed workflow files\n\n`;
799
+ body += `Generated by ai-flow-kit v${PKG_VERSION}. `;
800
+ body += `These are the full DEV/TESTER/EXECUTE (+ gen-doc/ingest-data), BA spec, and QA `;
801
+ body += `testcase gate workflows — too large to inline here. Read the relevant file `;
802
+ body += `**in full** before starting a gate; do not work from a summary and do not skip, `;
803
+ body += `shorten, or merge gates:\n\n`;
804
+ body += instructionPaths.map(p => `- \`${p}\``).join('\n');
805
+ }
806
+
807
+ return body.trim();
808
+ }
809
+
810
+ /**
811
+ * Assemble the marker block for one tool.
812
+ * 'inline' tools get the framework's gate workflow embedded inline; 'pointer'
813
+ * tools already got their "read from disk" note via buildSharedBlock() above.
814
+ * Pass includeShared: false for multi-framework projects — the tool header,
815
+ * skill registry, and workflow-files note are written once via
816
+ * buildSharedBlock() instead.
817
+ */
818
+ async function buildToolBlock(tool, blockMarkerStart, blockMarkerEnd, { includeShared = true } = {}) {
819
+ const pointer = resolveToolLayout(tool) === 'pointer';
820
+ let body = '';
821
+
822
+ if (includeShared) {
823
+ const shared = await buildSharedBlock(tool);
824
+ if (shared) body += shared + '\n\n';
825
+ }
826
+
827
+ if (frameworkContent) body += frameworkContent + '\n\n';
828
+
829
+ if (!pointer && workflowContent) {
725
830
  body += workflowContent + '\n\n';
726
831
  }
727
832
 
@@ -802,7 +907,7 @@ async function setupFramework(projectDir, framework, multi = false, selectedTool
802
907
  const blockMarkerStart = multi ? `<!-- aiflow-kit-start:${framework} -->` : markerStart;
803
908
  const blockMarkerEnd = multi ? `<!-- aiflow-kit-end:${framework} -->` : markerEnd;
804
909
 
805
- const finalContent = await buildToolBlock(tool, blockMarkerStart, blockMarkerEnd);
910
+ const finalContent = await buildToolBlock(tool, blockMarkerStart, blockMarkerEnd, { includeShared: !multi });
806
911
 
807
912
  const fileExists = await fs.pathExists(targetPath);
808
913
 
@@ -861,19 +966,15 @@ async function setupFramework(projectDir, framework, multi = false, selectedTool
861
966
  }
862
967
  }
863
968
  } else {
864
- if (fileExists) {
865
- const existingContent = await fs.readFile(targetPath, 'utf-8');
866
- const hasBlock = existingContent.includes(blockMarkerStart) && existingContent.includes(blockMarkerEnd);
867
- if (hasBlock) {
868
- const regex = new RegExp(`${blockMarkerStart}[\\s\\S]*?${blockMarkerEnd}`, 'g');
869
- const updatedContent = existingContent.replace(regex, finalContent);
870
- await fs.writeFile(targetPath, updatedContent);
871
- } else {
872
- await fs.appendFile(targetPath, separator + finalContent);
873
- }
874
- } else {
875
- await fs.writeFile(targetPath, finalContent);
969
+ // Upsert the shared block (tool header + skill registry) once per tool —
970
+ // fixed markers mean a second framework's call replaces it in place
971
+ // instead of appending another copy.
972
+ const sharedBody = await buildSharedBlock(tool);
973
+ if (sharedBody) {
974
+ const sharedFinal = `${sharedMarkerStart}\n${sharedBody}\n${sharedMarkerEnd}`;
975
+ await upsertMarkerBlock(targetPath, sharedMarkerStart, sharedMarkerEnd, sharedFinal, separator);
876
976
  }
977
+ await upsertMarkerBlock(targetPath, blockMarkerStart, blockMarkerEnd, finalContent, separator);
877
978
  written.push(AI_TOOL_FILES[tool]);
878
979
  }
879
980
  }
@@ -1523,6 +1624,8 @@ module.exports = init;
1523
1624
  module.exports.AI_TOOL_FILES = AI_TOOL_FILES;
1524
1625
  module.exports.AI_TOOL_LAYOUT = AI_TOOL_LAYOUT;
1525
1626
  module.exports.resolveToolLayout = resolveToolLayout;
1627
+ module.exports.resolveSkillListStyle = resolveSkillListStyle;
1628
+ module.exports.resolveSkillsRoot = resolveSkillsRoot;
1526
1629
  module.exports.writeWorkflowInstructions = writeWorkflowInstructions;
1527
1630
  module.exports.setupCodex = setupCodex;
1528
1631
  module.exports.setupCodexSkills = setupCodexSkills;
package/scripts/prompt.js CHANGED
@@ -386,16 +386,16 @@ Follow gates IN ORDER. Do NOT skip or merge gates.
386
386
 
387
387
  ### GATE 1 — Pre-flight & Work Plan (auto-start)
388
388
  **INVOKE:** \`execute-flow\` skill
389
- Check: MCP Playwright configured, TC file is parseable, \`ak-test/{repo}/\` scaffold exists, \`BASE_URL\` set, Playwright config exists. Display the work plan (TC file, repo, screen, BASE_URL) → wait for **APPROVED**.
389
+ Check: MCP Playwright configured, TC file is parseable, \`AK-Docs/03.Testing/\` subfolders exist (\`04.Evidence/\`, \`05.Scripts/\`, \`02.Reports/\`, \`06.Bugs/\`), \`BASE_URL\` set, \`AK-Docs/03.Testing/05.Scripts/{repo}/playwright.config.ts\` exists (auto-scaffold if missing), \`.gitignore\` covers \`03.Testing/04.Evidence/\` and \`05.Scripts/**/node_modules\`. Display the work plan (TC file, repo, screen, BASE_URL) → wait for **APPROVED**.
390
390
 
391
391
  ### GATE 2 — Script Sync
392
392
  Only runs after Gate 1 APPROVED.
393
393
  **INVOKE:** \`script-sync\` skill
394
- Hash-based TC↔script sync: new TC → generate Playwright test via MCP snapshot/locator (never fabricate selectors); changed TC → update; unchanged → skip; manual/blocked → mark Untest/Pending. Display new/updated/skipped/manual counts → wait for **APPROVED**.
394
+ Hash-based TC↔script sync: new TC → generate Playwright test via MCP snapshot/locator (never fabricate selectors, never use a locator matching more than one element); changed TC → update; unchanged → skip; manual/blocked → mark Untest/Pending. Every step calls \`captureStepEvidence()\` from \`Shared/evidence-helper.ts\`. Display new/updated/skipped/manual counts → wait for **APPROVED**.
395
395
 
396
396
  ### GATE 3 — Execute & Evidence
397
397
  Only runs after Gate 2 APPROVED.
398
- Run \`BASE_URL=<url> npx playwright test scripts/{screenId}/{ScreenID}.spec.ts\`, organize evidence (screenshots/trace/result.md) into \`ak-test/{repo}/results/{screenId}/run-{N}/\`, auto-draft bug reports for failures, update R1/R2 columns in the TC file. Supports \`RETEST: [TC_ID]\` and \`PR: [url]\`. Display pass/fail/untest/pending counts → wait for **APPROVED** (all Critical/High bugs resolved).
398
+ Run from \`AK-Docs/03.Testing/05.Scripts/\` with \`EVIDENCE_DIR\` pointed at \`AK-Docs/03.Testing/04.Evidence/{repo}/{featureDir}/run-{N}/\` (gitignored), organize evidence (screenshots/trace) there and text output (\`result.md\`, \`testreport.md\`) into \`AK-Docs/03.Testing/02.Reports/{repo}/{featureDir}/run-{N}/\`, auto-draft bug reports into \`AK-Docs/03.Testing/06.Bugs/{repo}/{featureDir}/run-{N}/\`, update R1/R2 columns in the TC file. Cross-check assertions against the TC's actual Expected Result before recording PASS/FAIL — don't trust Playwright's exit code alone. Supports \`RETEST: [TC_ID]\` and \`PR: [url]\`. Display pass/fail/untest/pending counts → wait for **APPROVED** (all Critical/High bugs resolved).
399
399
 
400
400
  ### GATE 4 — Report & Bug Logging (wait for APPROVED)
401
401
  Only runs after Gate 3 APPROVED.
@@ -18,7 +18,9 @@ const HARNESS_FILES = [
18
18
 
19
19
  const GITIGNORE_LINES = [
20
20
  '# Playwright',
21
+ 'node_modules/',
21
22
  'playwright-report/',
23
+ 'blob-report/',
22
24
  'test-results/',
23
25
  'tests/e2e/.auth/',
24
26
  '.env',