@haposoft/cafekit 0.7.8 → 0.7.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.
package/bin/install.js CHANGED
@@ -827,6 +827,43 @@ function copyRulesDirectory(platformKey, results, options = {}) {
827
827
  }
828
828
  }
829
829
 
830
+ // Ensure .gitignore configured at root
831
+ function ensureGitignore(results, options = {}) {
832
+ const gitignorePath = path.join(process.cwd(), '.gitignore');
833
+ const header = '# CafeKit / Ecosystem';
834
+ const patterns = [
835
+ 'specs/_shared/',
836
+ 'plans/',
837
+ '!plans/templates/'
838
+ ];
839
+
840
+ if (!fs.existsSync(gitignorePath)) {
841
+ const content = ['# Git Ignore', '', header, ...patterns, ''].join('\n');
842
+ fs.writeFileSync(gitignorePath, content, 'utf8');
843
+ console.log(' ✓ .gitignore created at root');
844
+ results.copied++;
845
+ return;
846
+ }
847
+
848
+ const content = fs.readFileSync(gitignorePath, 'utf8');
849
+ const lines = content.split('\n').map(l => l.trim());
850
+ const missing = patterns.filter(p => !lines.includes(p));
851
+
852
+ if (missing.length > 0) {
853
+ let newContent = content;
854
+ if (!newContent.endsWith('\n')) newContent += '\n';
855
+ if (!content.includes(header)) newContent += `\n${header}\n`;
856
+
857
+ newContent += missing.join('\n') + '\n';
858
+ fs.writeFileSync(gitignorePath, newContent, 'utf8');
859
+ console.log(` ↻ .gitignore updated: added ${missing.join(', ')}`);
860
+ results.updated++;
861
+ } else {
862
+ console.log(' → .gitignore already up to date');
863
+ results.skipped++;
864
+ }
865
+ }
866
+
830
867
  // ═══════════════════════════════════════════════════════════
831
868
  // GEMINI CLI SETUP
832
869
  // ═══════════════════════════════════════════════════════════
@@ -902,7 +939,7 @@ function configureGeminiKey(apiKey) {
902
939
  }
903
940
 
904
941
  // Luôn ghi trực tiếp key vào rốn của não bộ AI
905
- fs.writeFileSync(localEnvFile, `GEMINI_API_KEY=${apiKey}\nVISUAL_MODEL=gemma-4-31b-it\n`, { mode: 0o600 });
942
+ fs.writeFileSync(localEnvFile, `GEMINI_API_KEY=${apiKey}\nVISUAL_MODEL=gemma-4-31b-it\nSEARCH_MODEL=gemini-2.5-pro\n`, { mode: 0o600 });
906
943
  console.log(' ✓ Gemini API key configured securely in project (.claude/.env)');
907
944
 
908
945
  return true;
@@ -1024,6 +1061,12 @@ async function main() {
1024
1061
  console.log();
1025
1062
  }
1026
1063
 
1064
+ // Ensure root .gitignore is configured correctly
1065
+ console.log('Root Configuration');
1066
+ console.log('-'.repeat(40));
1067
+ ensureGitignore(results, installerOptions);
1068
+ console.log();
1069
+
1027
1070
  // Setup Gemini CLI and API Key config
1028
1071
  await setupGeminiCLI(platforms);
1029
1072
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haposoft/cafekit",
3
- "version": "0.7.8",
3
+ "version": "0.7.10",
4
4
  "description": "Spec-Driven Development workflow for AI coding assistants. Supports Claude Code and Antigravity with spec-first workflows plus Claude Code hapo: skills.",
5
5
  "author": "Haposoft <nghialt@haposoft.com>",
6
6
  "license": "MIT",
@@ -70,11 +70,11 @@ When you need to search the internet for information (research, docs lookup, tro
70
70
 
71
71
  | Priority | Tool | Command | When to use |
72
72
  |----------|------|---------|-------------|
73
- | 🥇 **P1** | `web-search.cjs` | `node .claude/scripts/web-search.cjs "query"` | **ALWAYS try first.** Works on ALL models via Gemini Google Search Grounding. Supports `--multi "q1" "q2"` for batch. Returns JSON with answer + sources. |
74
- | 🥈 **P2** | `WebSearch` (native) | Use WebSearch tool directly | Secondary verification, or when P1 fails/unavailable. |
75
- | 🥉 **P3** | `docs-fetch.js` | `node .claude/scripts/docs-fetch.js "library"` | Only when you already know a specific library and need its raw documentation. |
73
+ | 🥇 **P1** | `web-search.cjs` | `node .claude/scripts/web-search.cjs "[query]"` | **EXCLUSIVE PRIMARY.** Works via Gemini Grounding. Supports `--multi`. **TRUST THE SYNTHESIZED ANSWER** do NOT manually scrape source URLs. |
74
+ | 🥈 **P2** | `WebSearch` (native) | Use WebSearch tool directly | Secondary verification, or when P1 fails. |
75
+ | 🥉 **P3** | `docs-fetch.js` | `node .claude/scripts/docs-fetch.js "library"` | Only for fetching raw documentation when synthesis is insufficient. |
76
76
 
77
- **IMPORTANT**: When the user asks you to find information, research a topic, look up documentation, or investigate anything that requires internet access, you MUST use the Web Search Protocol above. Do NOT reply with "I cannot search the web" you have `web-search.cjs` available via Bash.
77
+ **IMPORTANT**: When the user asks you to find information, research a topic, or investigate anything that requires internet access, you MUST use the Web Search Protocol above. **NEVER** reply with "I cannot search the web". **NEVER** attempt manual `Fetch` or Python-based scraping for search results if `web-search.cjs` provides an answer. Trust the grounding.
78
78
 
79
79
  ## Code Refactoring Triggers
80
80
 
@@ -11,6 +11,8 @@ Goal: Catch the mistakes AI-written code commonly makes — logic errors, securi
11
11
 
12
12
  You DO NOT fix code. You only READ, SCORE, and REPORT.
13
13
 
14
+
15
+
14
16
  ## Pre-Review: Blast Radius Check (MANDATORY)
15
17
 
16
18
  Before reading any specific logic, you MUST run a Dependency Scope Check (Blast Radius):
@@ -20,6 +20,7 @@ Any logic gaps must be clarified BEFORE typing, not discovered after bugs ship.
20
20
  - **Surgical Reading (Large Files):** Never use blanket `Read` commands on files > 800 lines. Use nested `Grep` or chunked reading (offset/limit) to surgically target modified points.
21
21
  - **Component Scaffold Limit:** Any React/UI component file that exceeds 200 LOC must trigger a proactive modularization step (split into smaller child files).
22
22
 
23
+
23
24
  ## Self-Check Checklist (Before Reporting Complete)
24
25
 
25
26
  - [ ] Every async operation has explicit `try/catch` or `.catch()` — no silent failures allowed.
@@ -16,6 +16,7 @@ Unlike typical managers who report on "feelings" or conversational summaries, yo
16
16
  2. **Blocker Assassination:** You identify task stagnation (e.g., a spec stuck in 'in-progress' across multiple sessions) and force the immediate assignment of next-step actions.
17
17
  3. **Agile Aggregation:** When parallel sub-agents (like `god-developer` and `test-runner`) report completion, you sweep their logs, consolidate the facts, and generate a single authoritative **Feature Release Report**.
18
18
 
19
+
19
20
  ## Execution Constraints
20
21
 
21
22
  Before you declare any phase complete or issue a final status report, you must internally trace:
@@ -41,16 +41,21 @@ You possess extreme proficiency in:
41
41
  - Segregating Stable Production Practices away from Toxic Experimental Paradigms.
42
42
  - Sniffing out valid Adoption Patterns and real-world implementation trending.
43
43
  - Forgiving nothing when crafting Trade-off computational matrices for thousands of competing libraries.
44
- - **[PRIORITY 1]** Deploying `scripts/web-search.cjs` as the **PRIMARY search tool** for all web queries. Usage: `node .claude/scripts/web-search.cjs "query"` or `node .claude/scripts/web-search.cjs --multi "q1" "q2"`. Returns JSON with answer, sources, and citations via Gemini Google Search Grounding. ALWAYS attempt this first before any other search method.
45
- - **[PRIORITY 2]** If WebSearch native tool is available, use it as secondary verification or when `web-search.cjs` fails.
46
- - **[PRIORITY 3]** Deploying `scripts/docs-fetch.js` only when official Github/Doc URLs are already identified and you need to pull raw documentation content.
44
+ - **[PRIORITY 1]** Deploying `node .claude/scripts/web-search.cjs "[query]"` as the **EXCLUSIVE PRIMARY search tool**. This tool uses Gemini Grounding to return a synthesized **answer** plus cited sources. **STOP SEARCHING** once you have a sufficient answer from this script. Do NOT manually crawl source URLs if the provided synthesis is clear.
45
+ - **[PRIORITY 2]** Trust the script's output directly. READ the JSON and extract the `answer` field. **STRICTLY FORBIDDEN**: Writing Python scripts to parse this JSON or manually `Fetch` every URL listed in the sources unless the user explicitly demands a deep-dive implementation detail only found in a raw document.
46
+ - **[PRIORITY 3]** If `web-search.cjs` fails or returns no results, use native `WebSearch` tool (if available) as a backup.
47
+ - **[PRIORITY 4]** Deploying `scripts/docs-fetch.js` ONLY for raw documents where the direct URL is already known and synthesis is insufficient.
47
48
  - Deploying Bash and raw Grep utilities to surgically dissect embedded Document architectures and internal file payloads to evaluate raw insights.
48
49
 
49
50
  **ABSOLUTE IMMOVEABLE DIRECTIVE**: You are **STRICTLY PROHIBITED** from generating executable endpoint "Implementation Code". You exist ONLY to maneuver data streams, render synthesis Summary text, and return comprehensive Markdown documentation pathways to the main caller Agent.
50
51
 
51
- ## Report Output Format
52
+ ## Report Output Routing
52
53
 
53
- Adhere to the exact hierarchical naming injection logic provided by the Hook system (`## Naming`). Generated file paths must encompass explicit broadcasting timestamp references.
54
+ Save research output based on context:
55
+ - **Feature research** (active spec exists) → `specs/<feature>/research.md`
56
+ - **System-wide research** (no active spec) → `specs/_shared/Research-<slug>-<date>.md`
57
+
58
+ Do NOT save to `plans/reports/` or `docs/`. All research belongs in `specs/`.
54
59
 
55
60
  ## Team Operations Mode
56
61
 
@@ -20,6 +20,7 @@ You DO NOT write implementation code. You produce Specifications that downstream
20
20
  - **80/20 MVP:** Identify the 20% of features that deliver 80% of value.
21
21
  - **Systems Thinking:** How does this feature connect to (or break) existing systems?
22
22
 
23
+
23
24
  ## Pre-Completion Checklist
24
25
 
25
26
  Before finalizing any specification, assert:
@@ -19,6 +19,7 @@ You are an award-caliber UI/UX designer. You merge aesthetic excellence with eng
19
19
  - **Accessibility:** WCAG 2.1 AA compliance as a baseline, not an afterthought.
20
20
  - **3D/WebGL:** Three.js scene composition, shader development (when appropriate).
21
21
 
22
+
22
23
  ## Design Workflow
23
24
 
24
25
  ### Phase 1: Research & Trend Scouting
@@ -5,8 +5,8 @@
5
5
  Every subagent prompt **must** include these three paths:
6
6
 
7
7
  - **Work Context** — the git root containing the target files
8
- - **Reports Directory** — `{work_context}/plans/reports/`
9
- - **Plans Directory** — `{work_context}/plans/`
8
+ - **Specs Directory** — `{work_context}/specs/`
9
+ - **Docs Directory** — `{work_context}/docs/`
10
10
 
11
11
  When CWD and work context differ (e.g., editing files in a sibling project), always use the **work context** paths.
12
12
 
@@ -14,8 +14,8 @@ When CWD and work context differ (e.g., editing files in a sibling project), alw
14
14
  Example prompt:
15
15
  "Resolve the date-parsing regression.
16
16
  Work context: /repos/billing-service
17
- Reports: /repos/billing-service/plans/reports/
18
- Plans: /repos/billing-service/plans/"
17
+ Specs: /repos/billing-service/specs/
18
+ Docs: /repos/billing-service/docs/"
19
19
  ```
20
20
 
21
21
  ---
@@ -97,11 +97,11 @@ Files to modify: [list]
97
97
  Files to read for context: [list]
98
98
  Acceptance criteria: [list]
99
99
  Constraints: [any relevant constraints]
100
- Plan reference: [phase file path if applicable]
100
+ Spec reference: [spec folder path if applicable]
101
101
 
102
102
  Work context: [project path]
103
- Reports: [reports path]
104
- Plans: [plans path]
103
+ Specs: [specs path]
104
+ Docs: [docs path]
105
105
  ```
106
106
 
107
107
  ### Do
@@ -19,11 +19,7 @@ const fs = require('fs');
19
19
  // ---------------------------------------------------------------------------
20
20
  // ENV Resolution: .claude/.env → process.env
21
21
  // ---------------------------------------------------------------------------
22
- function resolveApiKey() {
23
- // Priority 1: Already in environment
24
- if (process.env.GEMINI_API_KEY) return process.env.GEMINI_API_KEY;
25
-
26
- // Priority 2: Project-local .claude/.env
22
+ function loadEnv() {
27
23
  const envPaths = [
28
24
  path.join(process.cwd(), '.claude', '.env'),
29
25
  path.join(process.cwd(), '..', '.claude', '.env'),
@@ -33,13 +29,21 @@ function resolveApiKey() {
33
29
  try {
34
30
  if (fs.existsSync(envPath)) {
35
31
  const content = fs.readFileSync(envPath, 'utf8');
36
- const match = content.match(/^GEMINI_API_KEY=(.+)$/m);
37
- if (match) return match[1].trim().replace(/^["']|["']$/g, '');
32
+ content.split(/\r?\n/).forEach(line => {
33
+ const match = line.match(/^([^=]+)=(.*)$/);
34
+ if (match) {
35
+ const key = match[1].trim();
36
+ const val = match[2].trim().replace(/^["']|["']$/g, '');
37
+ // Only set if not already present in environment
38
+ if (process.env[key] === undefined) {
39
+ process.env[key] = val;
40
+ }
41
+ }
42
+ });
43
+ return; // Loaded successfully, no need to check other paths
38
44
  }
39
45
  } catch { /* skip */ }
40
46
  }
41
-
42
- return null;
43
47
  }
44
48
 
45
49
  // ---------------------------------------------------------------------------
@@ -89,10 +93,38 @@ function callGemini(apiKey, query, model) {
89
93
  });
90
94
  }
91
95
 
96
+ // ---------------------------------------------------------------------------
97
+ // Resolve Vertex AI grounding redirect URLs to real URLs
98
+ // ---------------------------------------------------------------------------
99
+ function resolveRedirectUrl(url) {
100
+ return new Promise((resolve) => {
101
+ if (!url || !url.includes('grounding-api-redirect')) {
102
+ resolve(url);
103
+ return;
104
+ }
105
+
106
+ const protocol = url.startsWith('https') ? https : require('http');
107
+ const req = protocol.request(url, { method: 'HEAD', timeout: 5000 }, (res) => {
108
+ // Follow redirect chain - Location header has the real URL
109
+ resolve(res.headers.location || url);
110
+ });
111
+ req.on('error', () => resolve(url));
112
+ req.on('timeout', () => { req.destroy(); resolve(url); });
113
+ req.end();
114
+ });
115
+ }
116
+
117
+ async function resolveAllUrls(sources) {
118
+ return Promise.all(sources.map(async (src) => {
119
+ const realUrl = await resolveRedirectUrl(src.url);
120
+ return { ...src, url: realUrl };
121
+ }));
122
+ }
123
+
92
124
  // ---------------------------------------------------------------------------
93
125
  // Parse Grounding Metadata → Structured Output
94
126
  // ---------------------------------------------------------------------------
95
- function parseResponse(geminiResponse, query) {
127
+ async function parseResponse(geminiResponse, query) {
96
128
  const candidate = geminiResponse.candidates?.[0];
97
129
  if (!candidate) return { query, error: 'No candidates returned' };
98
130
 
@@ -100,11 +132,22 @@ function parseResponse(geminiResponse, query) {
100
132
  const meta = candidate.groundingMetadata || {};
101
133
 
102
134
  // Extract source URLs from groundingChunks
103
- const sources = (meta.groundingChunks || []).map(chunk => ({
135
+ let sources = (meta.groundingChunks || []).map(chunk => ({
104
136
  title: chunk.web?.title || 'Unknown',
105
137
  url: chunk.web?.uri || '',
106
138
  }));
107
139
 
140
+ // Resolve redirect URLs to real URLs
141
+ sources = await resolveAllUrls(sources);
142
+
143
+ // Deduplicate by resolved URL
144
+ const seen = new Set();
145
+ sources = sources.filter(s => {
146
+ if (seen.has(s.url)) return false;
147
+ seen.add(s.url);
148
+ return true;
149
+ });
150
+
108
151
  // Extract search queries used by the model
109
152
  const searchQueries = meta.webSearchQueries || [];
110
153
 
@@ -132,7 +175,8 @@ async function main() {
132
175
  process.exit(1);
133
176
  }
134
177
 
135
- const apiKey = resolveApiKey();
178
+ loadEnv();
179
+ const apiKey = process.env.GEMINI_API_KEY;
136
180
  if (!apiKey) {
137
181
  console.error(JSON.stringify({
138
182
  error: 'GEMINI_API_KEY not found. Set it in .claude/.env or environment variable.'
@@ -140,15 +184,20 @@ async function main() {
140
184
  process.exit(1);
141
185
  }
142
186
 
143
- // Use model from env or default to gemini-2.5-flash
144
- const model = process.env.SEARCH_MODEL || 'gemini-2.5-flash';
187
+ // Determine which model to use. User might configure MODEL or VISUAL_MODEL in their .env
188
+ let model = process.env.SEARCH_MODEL || process.env.MODEL || process.env.VISUAL_MODEL || 'gemini-2.5-flash';
189
+
190
+ // Google Search Grounding ONLY supports Gemini models (not Claude, not Gemma)
191
+ if (!model.toLowerCase().includes('gemini') && !model.toLowerCase().includes('learnlm')) {
192
+ model = 'gemini-2.5-flash'; // Fallback to safe search model
193
+ }
145
194
 
146
195
  const results = [];
147
196
 
148
197
  for (const query of queries) {
149
198
  try {
150
199
  const raw = await callGemini(apiKey, query, model);
151
- results.push(parseResponse(raw, query));
200
+ results.push(await parseResponse(raw, query));
152
201
  } catch (err) {
153
202
  results.push({ query, error: err.message });
154
203
  }
@@ -23,11 +23,11 @@ Call the `TaskCreate` tool to spin up the `researcher` subagent.
23
23
  **Instructions to pass to Researcher:**
24
24
  ```text
25
25
  Conduct comprehensive research on: [topic]
26
- Constraint 1: ALWAYS use `node .claude/scripts/web-search.cjs "query"` as PRIMARY search method (supports --multi for batch). This uses Gemini Google Search Grounding and returns JSON with answer + sources.
27
- Constraint 2: Use native WebSearch tool as secondary verification or when web-search.cjs fails.
28
- Constraint 3: Use scripts/docs-fetch.js ONLY when official Github/Doc URLs are already identified.
29
- Constraint 4: Limit total search calls to a maximum of 5 distinct queries to conserve context.
30
- Constraint 5: Validate information via cross-referencing capabilities.
26
+ Constraint 1: ALWAYS use `node .claude/scripts/web-search.cjs "[query]"` as the EXCLUSIVE primary search method. This tool uses Gemini Grounding and returns a synthesized answer + cited sources. Do NOT manually crawl source URLs if the script provides a sufficient answer.
27
+ Constraint 2: TRUST THE SYNTHESIS. The output contains the research results. Read the JSON and use the `answer` field directly. Do NOT write Python scripts to re-parse it or manually `Fetch` sources unless deep implementation details are missing.
28
+ Constraint 3: Use native WebSearch or manual Fetch ONLY if the script fails or returns no results.
29
+ Constraint 4: Limit total search calls to a maximum of 5 distinct queries.
30
+ Constraint 5: Stop excessive "chain-searching". Use the grounding answer as the definitive summary.
31
31
  Output Format: Must strictly follow the 'Standard Research Report' layout.
32
32
  ```
33
33
 
@@ -38,6 +38,16 @@ Instruct the Researcher Subagent with this strict requirement:
38
38
  > "Sử dụng nguyên bản template tại `packages/spec/src/claude/skills/specs/templates/research.md`. Tuyệt đối không tự ý đẻ thêm các đề mục ngoài phạm vi file template này."
39
39
 
40
40
  ## Post-Execution
41
- Once the `researcher` completes the Task and returns the Markdown output:
42
- 1. Save the file cleanly using the naming path format injected by the Context Hook (typically `plans/reports/Report-<slug>-<date>.md`).
43
- 2. Conclude the workflow by providing the user with the file path.
41
+ Once the `researcher` completes the Task and returns the Markdown output, save it based on context:
42
+
43
+ ### Output Routing
44
+ | Context | Save to | Example |
45
+ |---|---|---|
46
+ | Active spec exists (`specs/<feature>/`) | `specs/<feature>/research.md` | `specs/auth-login/research.md` |
47
+ | No active spec (system-wide / general) | `specs/_shared/Research-<slug>-<date>.md` | `specs/_shared/Research-mv3-best-practices-2026-04-11.md` |
48
+
49
+ ### Rules
50
+ 1. **Feature research** → Always save inside the active spec folder. If `specs/<feature>/` doesn't exist yet, create it.
51
+ 2. **System-wide research** → Save to `specs/_shared/`. Create the directory if it doesn't exist.
52
+ 3. **Never** save to `plans/reports/` or `docs/`. All research belongs in `specs/`.
53
+ 4. Conclude the workflow by providing the user with the saved file path.
@@ -402,8 +402,8 @@ async function main() {
402
402
  const session = JSON.parse(fs.readFileSync(sessionPath, 'utf8'));
403
403
  const planPath = session.activePlan?.trim();
404
404
  if (planPath) {
405
- // Extract slug from path like "plans/260106-1554-statusline-visual"
406
- const match = planPath.match(/plans\/\d+-\d+-(.+?)(?:\/|$)/);
405
+ // Extract slug from path like "specs/auth-login" or legacy "plans/260106-1554-feature"
406
+ const match = planPath.match(/(?:specs|plans)\/(?:\d+-\d+-)?(.+?)(?:\/|$)/);
407
407
  activePlan = match ? match[1] : planPath.split('/').pop();
408
408
  }
409
409
  }