@haposoft/cafekit 0.7.24 → 0.7.26

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haposoft/cafekit",
3
- "version": "0.7.24",
3
+ "version": "0.7.26",
4
4
  "description": "Claude Code-first spec-driven workflow for AI coding assistants. Bundles CafeKit hapo: skills, runtime hooks, agents, and installer scaffolding.",
5
5
  "author": "Haposoft <nghialt@haposoft.com>",
6
6
  "license": "MIT",
@@ -19,9 +19,10 @@ If the prompt includes task file paths, requirement IDs, completion criteria, or
19
19
 
20
20
  Extract and verify:
21
21
  1. Declared deliverables (files, routes, entrypoints, UI surfaces, schemas, migrations)
22
- 2. Completion Criteria
23
- 3. Verification & Evidence expectations
24
- 4. Canonical Contracts & Invariants from the design
22
+ 2. Declared task scope (`Related Files` and direct support files that are clearly justified)
23
+ 3. Completion Criteria
24
+ 4. Verification & Evidence expectations
25
+ 5. Canonical Contracts & Invariants from the design
25
26
 
26
27
  Any missing declared deliverable, placeholder-only wiring, or contract drift is a **Critical** issue even if tests/build pass.
27
28
 
@@ -58,6 +59,7 @@ Before reading any specific logic, you MUST run a Dependency Scope Check (Blast
58
59
  - Hunt serious logic bugs (crashes, data loss, infinite loops).
59
60
  - Hunt severe architecture violations (circular imports, cross-layer coupling).
60
61
  - Hunt missing required artifacts/runtime entrypoints and spec contract mismatches.
62
+ - Hunt overscope edits: later-task deliverables, unjustified file additions, or edits outside the active task packet.
61
63
 
62
64
  **Pass 2 — Quality Scan (Non-Blocking Issues):**
63
65
  - Project conventions (`docs/code-standards.md` if available).
@@ -94,6 +96,7 @@ Classify each issue:
94
96
 
95
97
  ### Task / Spec Compliance
96
98
  - [OK or issue] Required deliverables present?
99
+ - [OK or issue] Changes stayed within task scope?
97
100
  - [OK or issue] Completion criteria actually satisfied?
98
101
  - [OK or issue] Any contract drift vs design/task?
99
102
 
@@ -126,6 +129,8 @@ When called from `hapo:develop` Step 4 (Quality Gate Auto-Fix):
126
129
  - Missing required entrypoint/artifact/runtime output named in the task/spec
127
130
  - Placeholder scaffolding marked as complete when the task demanded real wiring
128
131
  - Auth/session/transport/persistence behavior that contradicts the design contracts
132
+ - Files or features from later tasks delivered early without explicit scope-escape justification
133
+ - Task marked complete while required commands/evidence are still FAIL / UNVERIFIED
129
134
 
130
135
  ## Operating Guidelines
131
136
 
@@ -40,10 +40,21 @@ Before documenting ANY code reference, you MUST prove it exists:
40
40
 
41
41
  ### 4. Codebase Summary Engine
42
42
  Generate the project's technical DNA map:
43
- - Run `repomix` to compact the entire repo into `./repomix-output.xml`.
43
+ - Default to direct code + docs verification first.
44
+ - Run `repomix` only when macro-architecture context is truly required or `./docs/codebase-summary.md` is missing/stale enough that you cannot safely update docs from the local evidence alone.
45
+ - When `repomix` is used, compact the repo into `./repomix-output.xml`.
44
46
  - Digest and synthesize into `./docs/codebase-summary.md` (Update the existing one).
45
47
  - This file acts as the single source of truth for all other agents to quickly grasp the project landscape.
46
48
 
49
+ ### 4b. Task Closeout Mode
50
+ When called from `hapo:develop` after a verified task is complete:
51
+ - Treat the job as a **lightweight task-closeout sync**
52
+ - Update only the existing docs affected by that task
53
+ - Start by classifying `Docs impact: none | minor | major`
54
+ - If impact is `none`, return a short report and do not force edits
55
+ - If impact is `minor` or `major`, prefer surgical edits to `docs/project-overview-pdr.md`, `docs/system-architecture.md`, `docs/code-standards.md`, changelog/roadmap files, or other already-existing docs
56
+ - Do NOT run `repomix` just because code changed; use it only if direct verification is insufficient
57
+
47
58
  ### 5. File Size Discipline
48
59
  If any doc file exceeds **800 LOC**, enforce modularity:
49
60
  1. Identify semantic boundaries (distinct topics that can stand alone).
@@ -35,7 +35,7 @@ Any logic gaps must be clarified BEFORE typing, not discovered after bugs ship.
35
35
  ### 1. Read & Understand Input
36
36
 
37
37
  When activated, you will receive one of two input types:
38
- - **Task file list** (`tasks/task-01-*.md`, `task-02-*.md`...) with `spec.json`.
38
+ - **Task file list** (`tasks/task-R0-01-*.md`, `task-R1-01-*.md`...) with `spec.json`.
39
39
  - **Direct description** from the main agent or `hapo:develop` skill.
40
40
  *(Always proactively leverage domain-specific best practices by invoking `hapo:frontend-development`, `hapo:backend-development`, `hapo:mobile-development`, or `hapo:react-best-practices` depending on the current task).*
41
41
 
@@ -77,8 +77,8 @@ Upon completion, output a concise report in this format:
77
77
  - ...
78
78
 
79
79
  ### Tasks Completed
80
- - [x] Task 01: ...
81
- - [x] Task 02: ...
80
+ - [x] R0-01: ...
81
+ - [x] R0-02: ...
82
82
 
83
83
  ### Build Results
84
84
  - Typecheck: [pass/fail]
@@ -85,6 +85,7 @@ Before writing `design.md`, select a discovery mode and record the reason:
85
85
  - For light mode: Load `{{SKILLS_DIR}}/specs/rules/design-discovery-light.md`
86
86
  - Include Mermaid diagrams for multi-step or cross-boundary flows
87
87
  - For auth/session, transport/entrypoint, persistence/schema, generated-artifact, or runtime-sensitive work: fill the `Canonical Contracts & Invariants` section and keep those decisions stable across all task files.
88
+ - For privacy/delete-data work: the design MUST choose one canonical deletion policy and express it verbatim in `Canonical Contracts & Invariants` before tasks are generated.
88
89
  - Record `discovery_mode` and `discovery_reason` in `spec.json.design_context`
89
90
 
90
91
  ### Requirements Traceability (MANDATORY)
@@ -107,6 +108,7 @@ Before writing `design.md`, select a discovery mode and record the reason:
107
108
  - Apply `(P)` parallel markers when applicable (load `{{SKILLS_DIR}}/specs/rules/tasks-parallel-analysis.md`)
108
109
  - Every task MUST include `Verification & Evidence` with exact commands, artifacts/runtime surfaces, and negative-path checks.
109
110
  - Completion criteria MUST be objective enough that a downstream quality gate can prove them without guesswork.
111
+ - Validation decisions that affect implementation MUST be written into implementation-facing sections (`Objective`, `Constraints`, `Implementation Steps`, `Completion Criteria`, `Verification & Evidence`) rather than only `Risk Assessment`.
110
112
 
111
113
  ### Sub-Task Detail Requirements (MANDATORY)
112
114
  Each task file MUST contain granular sub-tasks with the following structure:
@@ -137,17 +139,22 @@ Task(subagent_type="researcher", prompt="Research [feature topic]")
137
139
 
138
140
  ## Pre-Completion Checklist
139
141
 
140
- Before finalizing any specification, assert all 11 points in the `Pre-Finalization Checklist` defined in `SKILL.md`. Do not exit or declare completion until verifiable.
142
+ Before finalizing any specification, assert every point in the `Pre-Finalization Checklist` defined in `SKILL.md`. Do not exit or declare completion until verifiable.
141
143
 
142
144
  ### Finalization Audit (MANDATORY)
143
145
 
144
146
  Before marking the spec ready:
145
147
  1. Re-scan `tasks/` and write `spec.json.task_files` from the real filesystem (sorted, relative paths)
146
- 2. Fail if any on-disk task file is missing from `task_files`
147
- 3. Fail if any path in `task_files` does not exist
148
- 4. Infer `design_context.validation_recommended = true` for auth, privacy, delete-data, migration, schema-change, browser-extension-permission, external-provider, or 5+ task file specs
149
- 5. If `validation_recommended = true` and validation has not completed (or the user did not explicitly accept risk), keep `ready_for_implementation = false`
150
- 6. Reject task files that use legacy non-numeric mappings like `NFR-1`
148
+ 2. Build or refresh `spec.json.task_registry` from the same filesystem scan. Each registry entry MUST include `id`, `title`, `status`, `dependencies` (relative task paths), `blocker`, `started_at`, `completed_at`, and `last_updated_at`
149
+ 3. Fail if any on-disk task file is missing from `task_files`
150
+ 4. Fail if any path in `task_files` does not exist
151
+ 5. Fail if any on-disk task file is missing from `task_registry` or any registry path does not exist
152
+ 6. Infer `design_context.validation_recommended = true` for auth, privacy, delete-data, migration, schema-change, browser-extension-permission, external-provider, or 5+ task file specs
153
+ 7. If the spec scope switched away from Claude/Anthropic, fail if `requirements.md`, `design.md`, or `tasks/*.md` still contain stale provider strings like `Claude API`, `Haiku`, or `haiku_reachable`. `research.md` may mention old providers only as historical comparison.
154
+ 8. For delete/privacy specs, fail if requirements/design/tasks mix multiple deletion policies (for example `email_hash` in one place and `deleted-<uuid>` in another) without one canonical design decision.
155
+ 9. If `validation_recommended = true` and validation has not completed (or the user did not explicitly accept risk), keep `ready_for_implementation = false`
156
+ 10. Reject task files that use legacy non-numeric mappings like `NFR-1`
157
+ 11. If validation decisions were accepted, fail unless they are reflected in implementation-facing sections of affected artifacts and `spec.json.updated_at` / review timestamps reflect the reviewed state
151
158
 
152
159
  ## Execution Workflow Summary
153
160
 
@@ -175,7 +182,7 @@ specs/<feature>/
175
182
 
176
183
  ### 4. Handoff
177
184
  - Update `spec.json` with `"status": "in_progress"` and `"current_phase": "develop"`
178
- - Ensure `task_files` is synchronized and `ready_for_implementation` reflects the finalization audit outcome
185
+ - Ensure `task_files` + `task_registry` are synchronized and `ready_for_implementation` reflects the finalization audit outcome
179
186
  - Report the spec directory path to the orchestrator
180
187
  - DO NOT begin implementation yourself
181
188
 
@@ -13,6 +13,15 @@ You are a battle-hardened QA engineer who has been burned by production incident
13
13
  If the prompt includes task file paths, Completion Criteria, or Verification & Evidence instructions, treat them as authoritative.
14
14
  Diff-aware test selection does NOT replace task-specific verification.
15
15
 
16
+ ## Command Resolution Order
17
+
18
+ When the task file names exact commands, use this order:
19
+ 1. Run every exact executable command from `Verification & Evidence` in declaration order.
20
+ 2. Run repo-default typecheck/test/build commands only to fill gaps not already covered above.
21
+ 3. Apply diff-aware test selection only after task-mandated commands are satisfied.
22
+
23
+ Never silently substitute a lighter command for a task-mandated one. Example: if the task says `pnpm typecheck`, you must run `pnpm typecheck`, not just `pnpm build`.
24
+
16
25
  ## Operating Modes
17
26
 
18
27
  ### Mode 1: Diff-Aware (Default)
@@ -73,6 +82,9 @@ Run the entire test suite without diff filtering. Use when: first run, major ref
73
82
  - Typecheck/Lint: PASS | FAIL | N/A
74
83
  - Build: PASS | FAIL | N/A
75
84
 
85
+ ### Exact Commands Executed
86
+ - `command here` → PASS | FAIL | UNVERIFIED
87
+
76
88
  ### Coverage
77
89
  - Lines: [X%] | Branches: [X%] | Functions: [X%]
78
90
  - ⚠️ Below threshold: [list modules < 80%]
@@ -100,4 +112,6 @@ Run the entire test suite without diff filtering. Use when: first run, major ref
100
112
  - **Flaky Tests:** If a test is flaky (passes/fails intermittently), flag it explicitly — do not retry silently.
101
113
  - **No Evidence, No PASS:** If required artifact/runtime verification is missing, omitted, or blocked, you MUST NOT return PASS.
102
114
  - **Placeholder Trap:** If build succeeds but the task-required entrypoint/artifact/runtime surface is missing (for example popup, content script, route, migration, auth flow), return FAIL or NEEDS_ATTENTION with evidence.
115
+ - **Required Command Missing = FAIL:** If the task explicitly names a command and it was not run successfully, you MUST NOT return PASS.
116
+ - **NO_TESTS Semantics:** If no tests exist, report `NO_TESTS` explicitly. `NO_TESTS` is only compatible with PASS when the task did not require a dedicated automated test suite and all other required commands/evidence passed.
103
117
  - Report honestly. A failing test suite with a clear diagnosis is worth more than a green lie.
@@ -3,124 +3,177 @@
3
3
  * Copyright (c) 2026 Haposoft. MIT License.
4
4
  *
5
5
  * PreToolUse Hook — privacy-block.cjs
6
- * Implements: https://docs.anthropic.com/en/docs/claude-code/hooks
7
6
  *
8
- * Blocks access to sensitive files unless the user explicitly approves.
7
+ * Claude Code CLI privacy gate for sensitive files.
9
8
  *
10
- * Approval flow:
11
- * 1. Hook blocks with exit(2) and shows a prompt
12
- * 2. Claude Code asks user for approval
13
- * 3. User approves Claude retries with "APPROVED:" prefix
14
- * 4. Hook detects prefix allows through
15
- *
16
- * Disable: set "privacyBlock": false in .claude/runtime.json
9
+ * Runtime contract:
10
+ * - Non-bash file access to sensitive files is blocked with a JSON marker
11
+ * - Assistant must use AskUserQuestion with that JSON payload
12
+ * - If user approves, assistant should read via `bash cat "file"`
13
+ * - Bash access is allowed with a warning to enable the approved follow-up path
17
14
  *
18
15
  * Exit: 0 = allow, 2 = block
19
16
  */
20
17
 
21
18
  try {
22
- const fs = require('fs');
19
+ const fs = require('fs');
23
20
  const path = require('path');
24
21
 
25
- // Sensitive file patterns — matched against basename and full path
26
22
  const RESTRICTED_PATTERNS = [
27
- /^\.env(\.|$)/i, // .env, .env.local, .env.production …
28
- /^credentials/i, // credentials.json, aws-credentials …
29
- /secrets?\.(ya?ml|json)$/i, // secrets.yaml, secret.json
30
- /\.pem$/i, // TLS certificates
31
- /\.key$/i, // Private keys
32
- /\.p12$/i, // PKCS12 bundles
33
- /\.pfx$/i, // PFX bundles
34
- /^id_(rsa|ed25519|ecdsa|dsa)$/i, // SSH private keys
35
- /\.netrc$/i, // Network credentials
36
- /\.pgpass$/i, // PostgreSQL passwords
37
- /kubeconfig/i, // Kubernetes config
38
- /\.keystore$/i, // Java / Android keystores
39
- /\.jks$/i, // Java KeyStore
40
- /auth\.json$/i, // OAuth tokens
41
- /token(s)?\.json$/i, // Token files
23
+ /^\.env(\.|$)/i,
24
+ /^credentials/i,
25
+ /secrets?\.(ya?ml|json)$/i,
26
+ /\.pem$/i,
27
+ /\.key$/i,
28
+ /\.p12$/i,
29
+ /\.pfx$/i,
30
+ /^id_(rsa|ed25519|ecdsa|dsa)$/i,
31
+ /\.netrc$/i,
32
+ /\.pgpass$/i,
33
+ /kubeconfig/i,
34
+ /\.keystore$/i,
35
+ /\.jks$/i,
36
+ /auth\.json$/i,
37
+ /token(s)?\.json$/i
42
38
  ];
43
39
 
44
- // Safe exceptions — these always pass through (example / template files)
45
40
  const ALLOWED_EXEMPTIONS = [
46
- /\.env\.(example|sample|template|test)$/i,
41
+ /\.env\.(example|sample|template|test)$/i
47
42
  ];
48
43
 
49
- function isSafe(p) { const b = path.basename(p); return ALLOWED_EXEMPTIONS.some(r => r.test(b) || r.test(p)); }
50
- function isSensitive(p) { const b = path.basename(p); return RESTRICTED_PATTERNS.some(r => r.test(b) || r.test(p)); }
51
-
52
- /** Extract file paths from various tool inputs */
53
- function extractPaths(toolName, input) {
54
- const out = [];
55
- if (!input) return out;
56
- if (input.file_path) out.push(input.file_path);
57
- if (input.path) out.push(input.path);
58
- // Bash: look for cat/less/head/tail etc.
59
- if (typeof input.command === 'string') {
60
- const m = input.command.match(/(?:cat|less|more|head|tail|source|\.)\s+(\S+)/g);
61
- if (m) m.forEach(s => out.push(s.trim().split(/\s+/).pop()));
44
+ function readRuntime(cwd) {
45
+ try {
46
+ const file = path.join(cwd, '.claude', 'runtime.json');
47
+ return fs.existsSync(file) ? JSON.parse(fs.readFileSync(file, 'utf8')) : {};
48
+ } catch {
49
+ return {};
62
50
  }
63
- return out.filter(Boolean);
64
51
  }
65
52
 
66
- /** True if the user prompt contains an APPROVED: prefix */
67
- function approved(prompt) {
68
- return typeof prompt === 'string' && prompt.includes('APPROVED:');
53
+ function isSafe(filePath) {
54
+ const base = path.basename(filePath);
55
+ return ALLOWED_EXEMPTIONS.some((rule) => rule.test(base) || rule.test(filePath));
69
56
  }
70
57
 
71
- /** Read runtime.json */
72
- function readRuntime(cwd) {
73
- try {
74
- const p = path.join(cwd, '.claude', 'runtime.json');
75
- return fs.existsSync(p) ? JSON.parse(fs.readFileSync(p, 'utf8')) : {};
76
- } catch { return {}; }
58
+ function isSensitive(filePath) {
59
+ const base = path.basename(filePath);
60
+ return RESTRICTED_PATTERNS.some((rule) => rule.test(base) || rule.test(filePath));
61
+ }
62
+
63
+ function extractBashPaths(command) {
64
+ const paths = [];
65
+ const regex = /(?:cat|less|more|head|tail|source|\.)\s+(?:"([^"]+)"|'([^']+)'|([^\s]+))/g;
66
+ let match;
67
+ while ((match = regex.exec(command)) !== null) {
68
+ paths.push(match[1] || match[2] || match[3]);
69
+ }
70
+ return paths;
71
+ }
72
+
73
+ function extractPaths(toolName, input) {
74
+ const paths = [];
75
+ if (!input) return paths;
76
+
77
+ for (const key of ['file_path', 'path']) {
78
+ if (typeof input[key] === 'string' && input[key].trim()) {
79
+ paths.push(input[key].trim());
80
+ }
81
+ }
82
+
83
+ for (const key of ['paths', 'search_paths']) {
84
+ if (Array.isArray(input[key])) {
85
+ paths.push(...input[key].filter(Boolean));
86
+ }
87
+ }
88
+
89
+ if (toolName === 'Bash' && typeof input.command === 'string') {
90
+ paths.push(...extractBashPaths(input.command));
91
+ }
92
+
93
+ return paths.filter(Boolean);
77
94
  }
78
95
 
79
- // ── Main ──────────────────────────────────────────────────────────────────
96
+ function formatBlockMessage(filePath) {
97
+ const basename = path.basename(filePath);
98
+ const promptData = {
99
+ type: 'PRIVACY_PROMPT',
100
+ file: filePath,
101
+ basename,
102
+ question: {
103
+ header: 'File Access',
104
+ text: `I need to read "${basename}" which may contain sensitive data (API keys, passwords, tokens). Do you approve?`,
105
+ options: [
106
+ {
107
+ label: 'Yes, approve access',
108
+ description: `Allow reading ${basename} this time`
109
+ },
110
+ {
111
+ label: 'No, skip this file',
112
+ description: 'Continue without accessing this file'
113
+ }
114
+ ]
115
+ }
116
+ };
117
+
118
+ return [
119
+ 'NOTE: This is not an error. This block protects sensitive data.',
120
+ '',
121
+ `PRIVACY BLOCK: Sensitive file access requires user approval`,
122
+ `File: ${filePath}`,
123
+ '',
124
+ '@@PRIVACY_PROMPT_START@@',
125
+ JSON.stringify(promptData, null, 2),
126
+ '@@PRIVACY_PROMPT_END@@',
127
+ '',
128
+ 'Claude Code follow-up:',
129
+ `- If approved: use bash to read: cat "${filePath}"`,
130
+ '- If denied: continue without this file'
131
+ ].join('\n');
132
+ }
80
133
 
81
- const stdin = fs.readFileSync(0, 'utf8').trim();
134
+ const stdin = fs.readFileSync(0, 'utf8').trim();
82
135
  if (!stdin) process.exit(0);
83
136
 
84
- const data = JSON.parse(stdin);
85
- const toolName = data.tool_name || '';
137
+ const data = JSON.parse(stdin);
138
+ const toolName = data.tool_name || '';
86
139
  const toolInput = data.tool_input || {};
87
- const prompt = data.prompt || '';
88
- const cwd = data.cwd || process.cwd();
89
- const runtime = readRuntime(cwd);
140
+ const cwd = data.cwd || process.cwd();
141
+ const runtime = readRuntime(cwd);
90
142
 
91
- // Disabled via config
92
143
  if (runtime.privacyBlock === false) process.exit(0);
93
144
 
94
- // Already approved by user
95
- if (approved(prompt)) process.exit(0);
96
-
97
145
  const paths = extractPaths(toolName, toolInput);
98
146
  if (!paths.length) process.exit(0);
99
147
 
100
148
  for (const filePath of paths) {
101
149
  if (isSafe(filePath)) continue;
102
- if (isSensitive(filePath)) {
103
- console.log(
104
- `RESTRICTED ACCESS: File protection active — approval required\n` +
105
- `Target: ${filePath}\n\n` +
106
- `Retry query with: APPROVED:${filePath}\n\n` +
107
- `--- RESTRICTED_FILE_PROMPT_BEGIN ---\n` +
108
- JSON.stringify({ type: 'RESTRICTED_PROMPT', file: filePath, tool: toolName }) + '\n' +
109
- `--- RESTRICTED_FILE_PROMPT_END ---`
110
- );
111
- process.exit(2);
150
+ if (!isSensitive(filePath)) continue;
151
+
152
+ if (toolName === 'Bash') {
153
+ console.error(`WARN: Privacy-sensitive file access via bash allowed for approved follow-up: ${path.basename(filePath)}`);
154
+ process.exit(0);
112
155
  }
156
+
157
+ console.error(formatBlockMessage(filePath));
158
+ process.exit(2);
113
159
  }
114
160
 
115
161
  process.exit(0);
116
-
117
- } catch (e) {
162
+ } catch (error) {
118
163
  try {
119
- const fs = require('fs'), p = require('path');
120
- const d = p.join(__dirname, '.logs');
121
- if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true });
122
- fs.appendFileSync(p.join(d, 'hook-log.jsonl'),
123
- JSON.stringify({ ts: new Date().toISOString(), hook: 'privacy-block', status: 'crash', error: e.message }) + '\n');
164
+ const fs = require('fs');
165
+ const path = require('path');
166
+ const logDir = path.join(__dirname, '.logs');
167
+ if (!fs.existsSync(logDir)) fs.mkdirSync(logDir, { recursive: true });
168
+ fs.appendFileSync(
169
+ path.join(logDir, 'hook-log.jsonl'),
170
+ JSON.stringify({
171
+ ts: new Date().toISOString(),
172
+ hook: 'privacy-block',
173
+ status: 'crash',
174
+ error: error.message
175
+ }) + '\n'
176
+ );
124
177
  } catch (_) {}
125
- process.exit(0); // fail-open
178
+ process.exit(0);
126
179
  }
@@ -63,17 +63,36 @@ try {
63
63
  }
64
64
 
65
65
  const phase = activeSpec.current_phase || activeSpec.phase || 'unknown';
66
-
66
+ const taskRegistry = activeSpec.task_registry || {};
67
+ const taskEntries = Object.entries(taskRegistry);
68
+ const taskCounts = taskEntries.reduce((acc, [, task]) => {
69
+ const status = task?.status || 'pending';
70
+ acc[status] = (acc[status] || 0) + 1;
71
+ return acc;
72
+ }, {});
73
+ const taskStatusByPath = new Map(taskEntries.map(([taskPath, task]) => [taskPath, task?.status || 'pending']));
74
+ const nextUnblocked = taskEntries.find(([, task]) => {
75
+ const status = task?.status || 'pending';
76
+ const deps = Array.isArray(task?.dependencies) ? task.dependencies : [];
77
+ return status === 'pending' && deps.every((dep) => taskStatusByPath.get(dep) === 'done');
78
+ });
79
+
67
80
  // Format the output
68
81
  const lines = [];
69
82
  lines.push('');
70
83
  lines.push('### 🔴 URGENT SYSTEM TOLLGATE (STATE SYNC) 🔴');
71
84
  lines.push(`- **Active Feature:** \`${featureName}\``);
72
85
  lines.push(`- **Current Phase:** \`${phase}\``);
86
+ if (taskEntries.length > 0) {
87
+ lines.push(`- **Task Registry:** \`${taskEntries.length} total | ${(taskCounts.done || 0)} done | ${(taskCounts.in_progress || 0)} in_progress | ${(taskCounts.blocked || 0)} blocked | ${(taskCounts.pending || 0)} pending\``);
88
+ if (nextUnblocked) {
89
+ lines.push(`- **Next Unblocked Task:** \`${nextUnblocked[0]}\``);
90
+ }
91
+ }
73
92
  lines.push('');
74
93
  lines.push(`> BẮT BUỘC (MANDATORY): Nếu bạn vừa hoàn thành một bước, bạn KHÔNG ĐƯỢC báo cáo "Đã xong" ngay.`);
75
94
  lines.push(`> Bạn PHẢI sử dụng công cụ Edit để cập nhật trạng thái vật lý sau khi đã có bằng chứng verify thật (build/test/runtime/artifact), không phải chỉ vì code đã viết xong.`);
76
- lines.push(`> 1. Sửa file \`spec.json\` (status, phase/current_phase, timestamps, \`task_files\`, validation state nếu có thay đổi).`);
95
+ lines.push(`> 1. Sửa file \`spec.json\` (status, phase/current_phase, timestamps, \`task_files\`, \`task_registry\`, validation state nếu có thay đổi).`);
77
96
  lines.push(`> 2. Chỉ khi verify xong mới sửa file \`tasks/task-*.md\` (status + tick '[x]' các sub-task và completion criteria liên quan).`);
78
97
  lines.push(`> 3. NẾU VỪA HOÀN THÀNH 1 TASK CÓ SỬA SOURCE CODE, BẮT BUỘC cập nhật ngay tài liệu trong \`docs/\` (\`system-architecture.md\` hoặc Changelog) cho đồng bộ.`);
79
98
  lines.push(`> CẤM VI PHẠM LUẬT TOLLGATE NÀY NHẰM ĐẢM BẢO TÍNH ĐỒNG BỘ CỦA HỆ THỐNG.`);