product-playbook 1.1.1 → 1.2.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.es.md +12 -0
- package/README.ja.md +12 -0
- package/README.ko.md +12 -0
- package/README.md +12 -0
- package/README.zh-CN.md +12 -0
- package/README.zh-TW.md +12 -0
- package/commands/product-dev.md +10 -7
- package/hooks/hooks.json +40 -0
- package/hooks/pre-write-planning-gate.py +102 -0
- package/hooks/session-start-load-progress.py +80 -0
- package/hooks/user-prompt-detect-topic-switch.py +123 -0
- package/package.json +2 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "product-playbook",
|
|
3
3
|
"description": "MUST use when user wants to plan or strategize a product/feature. 22 PM frameworks, 6 modes, multi-language, from idea to dev handoff",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.2.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Charles Chen"
|
|
7
7
|
},
|
package/README.es.md
CHANGED
|
@@ -280,6 +280,18 @@ Genera un paquete completo de handoff de desarrollo e inicia desarrollo en Claud
|
|
|
280
280
|
> Por favor lee CLAUDE.md y TASKS.md, comienza ejecutando la Fase 0
|
|
281
281
|
```
|
|
282
282
|
|
|
283
|
+
### 🪝 Hooks de Ciclo de Vida
|
|
284
|
+
|
|
285
|
+
Tres hooks del plugin convierten las reglas centrales del playbook de "Claude debe recordarlas" a "el harness las aplica de forma determinista". Todos los hooks inyectan recordatorios suaves vía `systemMessage` — **ninguno bloquea al usuario**.
|
|
286
|
+
|
|
287
|
+
| Evento | Disparador | Acción |
|
|
288
|
+
|--------|-----------|--------|
|
|
289
|
+
| `SessionStart` | Cada sesión nueva o reanudada | Inyecta automáticamente `.product-playbook-progress.md` y `.product-context.md` en el contexto del modelo, para que una sesión de planificación interrumpida se reanude exactamente desde el paso pausado |
|
|
290
|
+
| `UserPromptSubmit` | Cada prompt durante una sesión de planificación activa | Detecta (a) prompts off-topic (debug / error / "arregla este código") y recuerda a Claude aplicar la regla de guardar progreso de SKILL.md; (b) palabras clave de intención de cambio (`改 step 2`, `update persona`, `rehacer JTBD`) y recuerda aplicar las reglas de Change Propagation |
|
|
291
|
+
| `PreToolUse` (Write/Edit/MultiEdit) | Antes de cada escritura de archivo | Si el proyecto sigue en fase de planificación (sin marcador `.product-dev-active`) y el destino es código fuente (`.ts/.tsx/.py/.go/...`), recuerda a Claude que la planificación produce documentos, no código. El marcador se crea automáticamente al ejecutar `/product-dev` |
|
|
292
|
+
|
|
293
|
+
Los hooks se cargan automáticamente desde `hooks/hooks.json` al instalar el plugin. Operan como no-op fuera de proyectos product-playbook, por lo que instalar el plugin no afecta a otros codebases.
|
|
294
|
+
|
|
283
295
|
### 📄 Importación y Exportación de Documentos
|
|
284
296
|
|
|
285
297
|
**Importa** cualquier documento existente al flujo de planificación — sin copiar y pegar manualmente:
|
package/README.ja.md
CHANGED
|
@@ -281,6 +281,18 @@ MVPを修正 → User Stories、DBスキーマ、プロダクトスペックサ
|
|
|
281
281
|
> CLAUDE.mdとTASKS.mdを読んで、Phase 0の実行を開始してください
|
|
282
282
|
```
|
|
283
283
|
|
|
284
|
+
### 🪝 ライフサイクルフック
|
|
285
|
+
|
|
286
|
+
3つのプラグインフックで、Playbook の中核ルールを「Claude の記憶頼み」から「ハーネスによる強制実行」へ昇格させます。すべてのフックは `systemMessage` によるソフトリマインダーを注入するのみで、**ユーザーを止めることはありません**。
|
|
287
|
+
|
|
288
|
+
| イベント | トリガー | 役割 |
|
|
289
|
+
|---------|---------|------|
|
|
290
|
+
| `SessionStart` | 新規 / 再開セッションごと | `.product-playbook-progress.md` と `.product-context.md` を自動でモデルの context に注入し、中断した企画を直前のステップから途切れなく再開 |
|
|
291
|
+
| `UserPromptSubmit` | 企画進行中のプロンプト送信ごと | (a) 脱線メッセージ(debug / エラー / "このコード直して")を検出して SKILL.md の進捗保存ルールを実行するよう Claude に指示。(b) 変更意図キーワード(`改 step 2`、`update persona`、`JTBD やり直し`)を検出して Change Propagation ルールの適用を促す |
|
|
292
|
+
| `PreToolUse` (Write/Edit/MultiEdit) | ファイル書き込み前 | プロジェクトが企画フェーズ(`.product-dev-active` マーカー無し)で、対象がソースコード拡張子(`.ts/.tsx/.py/.go/...`)の場合、「企画はドキュメントのみ、コードは生成しない」と Claude にリマインド。マーカーは `/product-dev` 実行時に自動生成 |
|
|
293
|
+
|
|
294
|
+
フックはプラグインインストール時に `hooks/hooks.json` から自動ロードされます。product-playbook プロジェクト外では完全に no-op で動作するため、他のコードベースへの影響はありません。
|
|
295
|
+
|
|
284
296
|
### 📄 ドキュメントインポート&エクスポート
|
|
285
297
|
|
|
286
298
|
**インポート** — 既存のドキュメントを企画フローに直接取り込み、手動コピペ不要:
|
package/README.ko.md
CHANGED
|
@@ -280,6 +280,18 @@ MVP 수정 → User Stories, DB Schema, 제품 스펙 요약 자동 업데이
|
|
|
280
280
|
> CLAUDE.md와 TASKS.md를 읽고, Phase 0 실행 시작
|
|
281
281
|
```
|
|
282
282
|
|
|
283
|
+
### 🪝 라이프사이클 훅
|
|
284
|
+
|
|
285
|
+
세 개의 플러그인 훅이 Playbook의 핵심 규칙을 "Claude가 알아서 기억"에서 "하네스가 강제 실행"으로 끌어올립니다. 모든 훅은 `systemMessage` 형태의 소프트 리마인더만 주입하며, **사용자를 막지 않습니다**.
|
|
286
|
+
|
|
287
|
+
| 이벤트 | 트리거 시점 | 역할 |
|
|
288
|
+
|--------|------------|------|
|
|
289
|
+
| `SessionStart` | 새 세션 또는 재개 시마다 | `.product-playbook-progress.md`와 `.product-context.md`를 자동으로 모델 context에 주입하여, 중단된 기획을 직전 단계에서 매끄럽게 이어가도록 함 |
|
|
290
|
+
| `UserPromptSubmit` | 기획 진행 중 프롬프트 제출마다 | (a) 주제 이탈 메시지(debug / 에러 / "이 코드 고쳐주세요")를 감지해 SKILL.md의 진행 상황 저장 규칙을 따르도록 Claude에 지시. (b) 변경 의도 키워드(`改 step 2`, `update persona`, `JTBD 다시 작성`)를 감지해 Change Propagation 규칙 적용을 환기 |
|
|
291
|
+
| `PreToolUse` (Write/Edit/MultiEdit) | 파일 쓰기 전마다 | 프로젝트가 기획 단계(`.product-dev-active` 마커 없음)이고 대상이 소스 코드 확장자(`.ts/.tsx/.py/.go/...`)일 경우, "기획은 문서만 생성, 코드는 생성하지 않는다"는 원칙을 Claude에 리마인드. 마커는 `/product-dev` 실행 시 자동 생성됨 |
|
|
292
|
+
|
|
293
|
+
훅은 플러그인 설치 시 `hooks/hooks.json`에서 자동 로드됩니다. product-playbook 프로젝트가 아닌 곳에서는 완전히 no-op이므로 다른 codebase에 영향을 주지 않습니다.
|
|
294
|
+
|
|
283
295
|
### 📄 문서 가져오기 및 내보내기
|
|
284
296
|
|
|
285
297
|
**가져오기** — 기존 문서를 기획 플로우에 직접 투입, 수동 복사 붙여넣기 불필요:
|
package/README.md
CHANGED
|
@@ -280,6 +280,18 @@ Generate a complete dev handoff package and kick off Claude Code development wit
|
|
|
280
280
|
> Please read CLAUDE.md and TASKS.md, start executing Phase 0
|
|
281
281
|
```
|
|
282
282
|
|
|
283
|
+
### 🪝 Lifecycle Hooks
|
|
284
|
+
|
|
285
|
+
Three plugin hooks turn the playbook's core rules from "Claude needs to remember" into harness-enforced behavior. All hooks emit advisory `systemMessage` reminders — none of them block the user.
|
|
286
|
+
|
|
287
|
+
| Event | Trigger | What it does |
|
|
288
|
+
|-------|---------|--------------|
|
|
289
|
+
| `SessionStart` | Every new / resumed session | Auto-injects `.product-playbook-progress.md` and `.product-context.md` into the model's context so a planning session resumes from the exact step it was paused at. |
|
|
290
|
+
| `UserPromptSubmit` | Each user prompt during an active planning session | Detects (a) off-topic prompts (debug / error / "fix this code") and reminds Claude to follow the off-topic save-progress rule, and (b) change-intent keywords (`改 step 2`, `update persona`, `重做 JTBD`) and reminds Claude to apply the Change Propagation rules. |
|
|
291
|
+
| `PreToolUse` (Write/Edit/MultiEdit) | Each file-write attempt | If the project is still in planning mode (no `.product-dev-active` marker) and the target is a source-code file (`.ts/.tsx/.py/.go/...`), reminds Claude that planning produces docs, not code. The marker is auto-created when `/product-dev` runs. |
|
|
292
|
+
|
|
293
|
+
Hooks are auto-loaded from `hooks/hooks.json` when the plugin is installed. They no-op outside playbook projects, so installing the plugin has zero effect on unrelated codebases.
|
|
294
|
+
|
|
283
295
|
### 📄 Document Import & Export
|
|
284
296
|
|
|
285
297
|
**Import** any existing document into the planning flow — no manual copy-paste:
|
package/README.zh-CN.md
CHANGED
|
@@ -280,6 +280,18 @@ product-playbook/
|
|
|
280
280
|
> 请读取 CLAUDE.md 和 TASKS.md,开始执行 Phase 0
|
|
281
281
|
```
|
|
282
282
|
|
|
283
|
+
### 🪝 生命周期 Hooks
|
|
284
|
+
|
|
285
|
+
三个 plugin hook 将 playbook 的核心规则从「靠 Claude 自己记得」转为「由 harness 强制执行」。所有 hook 只注入 `systemMessage` 软提醒,**不阻挡用户**。
|
|
286
|
+
|
|
287
|
+
| 事件 | 触发时机 | 作用 |
|
|
288
|
+
|------|---------|------|
|
|
289
|
+
| `SessionStart` | 每次新 session 或 resume | 自动将 `.product-playbook-progress.md` 与 `.product-context.md` 注入模型 context,让中断的规划从原步骤无缝接续 |
|
|
290
|
+
| `UserPromptSubmit` | 规划进行中每次提交 prompt | 检测(a)离题消息(debug / 错误 / "帮我改 code")→ 提醒 Claude 执行 SKILL.md 的存档规则;(b)变更意图关键字(`改 step 2`、`update persona`、`重做 JTBD`)→ 提醒套用 Change Propagation 规则 |
|
|
291
|
+
| `PreToolUse` (Write/Edit/MultiEdit) | 每次写文件前 | 若项目仍在规划阶段(无 `.product-dev-active` 标记)且目标是源代码后缀(`.ts/.tsx/.py/.go/...`),提醒 Claude「规划只产文档、不产 code」。`/product-dev` 执行时会自动建立该标记 |
|
|
292
|
+
|
|
293
|
+
Hooks 由 `hooks/hooks.json` 在 plugin 安装时自动加载。在非 product-playbook 项目中完全 no-op,安装 plugin 不会影响其他 codebase。
|
|
294
|
+
|
|
283
295
|
### 📄 文档导入与导出
|
|
284
296
|
|
|
285
297
|
**导入**任何现有文档到规划流程中 — 无需手动复制粘贴:
|
package/README.zh-TW.md
CHANGED
|
@@ -280,6 +280,18 @@ product-playbook/
|
|
|
280
280
|
> 請讀取 CLAUDE.md 和 TASKS.md,開始執行 Phase 0
|
|
281
281
|
```
|
|
282
282
|
|
|
283
|
+
### 🪝 生命週期 Hooks
|
|
284
|
+
|
|
285
|
+
三個 plugin hook 將 playbook 的核心規則從「靠 Claude 自己記得」轉為「由 harness 強制執行」。所有 hook 只注入 `systemMessage` 軟提醒,**不阻擋使用者**。
|
|
286
|
+
|
|
287
|
+
| 事件 | 觸發時機 | 作用 |
|
|
288
|
+
|------|---------|------|
|
|
289
|
+
| `SessionStart` | 每次新 session 或 resume | 自動將 `.product-playbook-progress.md` 與 `.product-context.md` 注入模型 context,讓中斷的規劃從原步驟無縫接續 |
|
|
290
|
+
| `UserPromptSubmit` | 規劃進行中每次送出 prompt | 偵測(a)離題訊息(debug / 錯誤 / "幫我改 code")→ 提醒 Claude 執行 SKILL.md 的存檔規則;(b)變更意圖關鍵字(`改 step 2`、`update persona`、`重做 JTBD`)→ 提醒套用 Change Propagation 規則 |
|
|
291
|
+
| `PreToolUse` (Write/Edit/MultiEdit) | 每次寫檔前 | 若專案仍在規劃階段(無 `.product-dev-active` 標記)且目標是原始碼副檔名(`.ts/.tsx/.py/.go/...`),提醒 Claude「規劃只產文件、不產 code」。`/product-dev` 執行時會自動建立該標記 |
|
|
292
|
+
|
|
293
|
+
Hooks 由 `hooks/hooks.json` 在 plugin 安裝時自動載入。在非 product-playbook 專案中完全 no-op,安裝 plugin 不會影響其他 codebase。
|
|
294
|
+
|
|
283
295
|
### 📄 文件匯入與匯出
|
|
284
296
|
|
|
285
297
|
**匯入**任何現有文件到規劃流程中 — 無需手動複製貼上:
|
package/commands/product-dev.md
CHANGED
|
@@ -10,12 +10,15 @@ Then read the following reference files in order:
|
|
|
10
10
|
|
|
11
11
|
Based on the product planning content completed in the current conversation, generate the full dev handoff package:
|
|
12
12
|
1. Confirm the tech stack (if not specified by the user, recommend one based on product characteristics)
|
|
13
|
-
2.
|
|
14
|
-
3. Generate
|
|
15
|
-
4. Generate
|
|
16
|
-
5. Generate
|
|
17
|
-
6. Generate docs/
|
|
18
|
-
7. Generate
|
|
19
|
-
8.
|
|
13
|
+
2. Create the `.product-dev-active` marker file at the project root (empty file). This signals the plugin's PreToolUse hook that the project has officially entered the dev-handoff phase, so subsequent source-code writes are no longer gated.
|
|
14
|
+
3. Generate CLAUDE.md (Claude Code project memory)
|
|
15
|
+
4. Generate TASKS.md (feature breakdown + phased releases + acceptance criteria)
|
|
16
|
+
5. Generate TICKETS.md (ticket list)
|
|
17
|
+
6. Generate docs/ARCHITECTURE.md (directory structure + DB Schema + API Endpoints)
|
|
18
|
+
7. Generate docs/PRD.md + docs/PRODUCT-SPEC.md
|
|
19
|
+
8. Generate scripts/setup.sh (one-click initialization)
|
|
20
|
+
9. Display Claude Code transition guide
|
|
20
21
|
|
|
21
22
|
If no product planning content exists in the conversation, prompt the user to run a product planning flow first.
|
|
23
|
+
|
|
24
|
+
Note: `.product-dev-active` is a session-local marker (gitignored by the plugin). Delete it if the project ever returns to planning-only mode.
|
package/hooks/hooks.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "product-playbook hooks: auto-load planning state, detect off-topic / change-propagation prompts, and gate code-file writes during the planning phase.",
|
|
3
|
+
"hooks": {
|
|
4
|
+
"SessionStart": [
|
|
5
|
+
{
|
|
6
|
+
"matcher": "startup|resume",
|
|
7
|
+
"hooks": [
|
|
8
|
+
{
|
|
9
|
+
"type": "command",
|
|
10
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/session-start-load-progress.py",
|
|
11
|
+
"timeout": 10
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"UserPromptSubmit": [
|
|
17
|
+
{
|
|
18
|
+
"hooks": [
|
|
19
|
+
{
|
|
20
|
+
"type": "command",
|
|
21
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/user-prompt-detect-topic-switch.py",
|
|
22
|
+
"timeout": 5
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
"PreToolUse": [
|
|
28
|
+
{
|
|
29
|
+
"matcher": "Write|Edit|MultiEdit",
|
|
30
|
+
"hooks": [
|
|
31
|
+
{
|
|
32
|
+
"type": "command",
|
|
33
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/pre-write-planning-gate.py",
|
|
34
|
+
"timeout": 5
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""PreToolUse hook: gate code-file writes during the planning phase.
|
|
3
|
+
|
|
4
|
+
The product-playbook plugin draws a hard line between PLANNING (produces
|
|
5
|
+
docs) and DEVELOPMENT (produces code). Until the user runs `/product-dev`
|
|
6
|
+
to enter the dev-handoff phase — which creates a `.product-dev-active`
|
|
7
|
+
marker file — Claude should not be writing source code.
|
|
8
|
+
|
|
9
|
+
This hook fires on Write / Edit / MultiEdit. If:
|
|
10
|
+
• a planning session is in progress (progress file exists, not complete)
|
|
11
|
+
• AND the dev-active marker is absent
|
|
12
|
+
• AND the target path looks like source code
|
|
13
|
+
|
|
14
|
+
…it injects an advisory `systemMessage` reminding Claude to finish
|
|
15
|
+
planning first. The hook does NOT block the tool call (permissionDecision
|
|
16
|
+
remains unset / allowed) so the user retains an explicit override path.
|
|
17
|
+
|
|
18
|
+
Doc/text files (.md, .txt, .json, etc.) are always allowed — those are
|
|
19
|
+
expected planning artifacts.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
import os
|
|
26
|
+
import re
|
|
27
|
+
import sys
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
|
|
30
|
+
CODE_EXTENSIONS = {
|
|
31
|
+
".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs",
|
|
32
|
+
".py", ".rb", ".php", ".go", ".rs", ".java", ".kt",
|
|
33
|
+
".swift", ".cs", ".cpp", ".cc", ".c", ".h", ".hpp",
|
|
34
|
+
".vue", ".svelte", ".scala", ".dart", ".m", ".mm",
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
WATCHED_TOOLS = {"Write", "Edit", "MultiEdit"}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _planning_in_progress(progress_file: Path) -> bool:
|
|
41
|
+
if not progress_file.is_file():
|
|
42
|
+
return False
|
|
43
|
+
try:
|
|
44
|
+
body = progress_file.read_text(encoding="utf-8", errors="ignore")
|
|
45
|
+
except OSError:
|
|
46
|
+
return False
|
|
47
|
+
lowered = body.lower()
|
|
48
|
+
if "status: complete" in lowered or "status:complete" in lowered:
|
|
49
|
+
return False
|
|
50
|
+
if re.search(r"status\s*[::]\s*completed", lowered):
|
|
51
|
+
return False
|
|
52
|
+
return True
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _target_path(tool_name: str, tool_input: dict) -> str | None:
|
|
56
|
+
if not isinstance(tool_input, dict):
|
|
57
|
+
return None
|
|
58
|
+
return tool_input.get("file_path") or tool_input.get("path")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def main() -> int:
|
|
62
|
+
try:
|
|
63
|
+
payload = json.load(sys.stdin)
|
|
64
|
+
except (json.JSONDecodeError, ValueError):
|
|
65
|
+
return 0
|
|
66
|
+
|
|
67
|
+
tool_name = payload.get("tool_name")
|
|
68
|
+
if tool_name not in WATCHED_TOOLS:
|
|
69
|
+
return 0
|
|
70
|
+
|
|
71
|
+
cwd = Path(payload.get("cwd") or os.getcwd())
|
|
72
|
+
|
|
73
|
+
# Dev phase active → silent passthrough.
|
|
74
|
+
if (cwd / ".product-dev-active").is_file():
|
|
75
|
+
return 0
|
|
76
|
+
|
|
77
|
+
if not _planning_in_progress(cwd / ".product-playbook-progress.md"):
|
|
78
|
+
return 0
|
|
79
|
+
|
|
80
|
+
target = _target_path(tool_name, payload.get("tool_input") or {})
|
|
81
|
+
if not target:
|
|
82
|
+
return 0
|
|
83
|
+
|
|
84
|
+
suffix = Path(target).suffix.lower()
|
|
85
|
+
if suffix not in CODE_EXTENSIONS:
|
|
86
|
+
return 0
|
|
87
|
+
|
|
88
|
+
message = (
|
|
89
|
+
f"[product-playbook] Heads-up: '{target}' looks like source code, "
|
|
90
|
+
"but this project is still in the PLANNING phase (no "
|
|
91
|
+
"`.product-dev-active` marker). The plugin's contract is "
|
|
92
|
+
"planning-produces-docs, dev-produces-code. Recommended: finish "
|
|
93
|
+
"the current mode's remaining steps, then run `/product-dev` to "
|
|
94
|
+
"enter the dev-handoff phase. If the user has explicitly asked "
|
|
95
|
+
"for code now, acknowledge the override and proceed."
|
|
96
|
+
)
|
|
97
|
+
json.dump({"systemMessage": message}, sys.stdout)
|
|
98
|
+
return 0
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
if __name__ == "__main__":
|
|
102
|
+
sys.exit(main())
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""SessionStart hook: auto-load product-playbook progress / context.
|
|
3
|
+
|
|
4
|
+
Detects `.product-playbook-progress.md` and `.product-context.md` in the
|
|
5
|
+
session's working directory. If either exists, their contents are injected
|
|
6
|
+
as additional context so Claude can resume the planning flow without
|
|
7
|
+
re-reading them manually.
|
|
8
|
+
|
|
9
|
+
Hook contract:
|
|
10
|
+
- Reads JSON payload from stdin (Claude Code provides session metadata).
|
|
11
|
+
- On exit code 0, stdout is fed back to Claude as session context.
|
|
12
|
+
- Silent (no output) when no playbook state is found, so non-PM projects
|
|
13
|
+
are unaffected.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
import os
|
|
20
|
+
import sys
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
MAX_BYTES_PER_FILE = 16 * 1024 # cap each file to keep injected context small
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _read_capped(path: Path) -> str | None:
|
|
27
|
+
try:
|
|
28
|
+
data = path.read_text(encoding="utf-8")
|
|
29
|
+
except OSError:
|
|
30
|
+
return None
|
|
31
|
+
if len(data) > MAX_BYTES_PER_FILE:
|
|
32
|
+
data = data[:MAX_BYTES_PER_FILE] + "\n... (truncated by hook)\n"
|
|
33
|
+
return data
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def main() -> int:
|
|
37
|
+
try:
|
|
38
|
+
payload = json.load(sys.stdin)
|
|
39
|
+
except (json.JSONDecodeError, ValueError):
|
|
40
|
+
payload = {}
|
|
41
|
+
|
|
42
|
+
cwd = payload.get("cwd") or os.getcwd()
|
|
43
|
+
cwd_path = Path(cwd)
|
|
44
|
+
|
|
45
|
+
targets = [
|
|
46
|
+
("Planning progress", cwd_path / ".product-playbook-progress.md"),
|
|
47
|
+
("Product context", cwd_path / ".product-context.md"),
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
sections: list[str] = []
|
|
51
|
+
for label, path in targets:
|
|
52
|
+
if not path.is_file():
|
|
53
|
+
continue
|
|
54
|
+
body = _read_capped(path)
|
|
55
|
+
if body is None:
|
|
56
|
+
continue
|
|
57
|
+
sections.append(f"### {label} ({path.name})\n\n{body}")
|
|
58
|
+
|
|
59
|
+
if not sections:
|
|
60
|
+
return 0
|
|
61
|
+
|
|
62
|
+
header = (
|
|
63
|
+
"[product-playbook] Existing planning state was detected in this "
|
|
64
|
+
"project. Resume from where the user left off — do NOT restart from "
|
|
65
|
+
"step 1. Reference the snapshot below before responding."
|
|
66
|
+
)
|
|
67
|
+
additional_context = header + "\n\n" + "\n\n---\n\n".join(sections)
|
|
68
|
+
|
|
69
|
+
output = {
|
|
70
|
+
"hookSpecificOutput": {
|
|
71
|
+
"hookEventName": "SessionStart",
|
|
72
|
+
"additionalContext": additional_context,
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
json.dump(output, sys.stdout)
|
|
76
|
+
return 0
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
if __name__ == "__main__":
|
|
80
|
+
sys.exit(main())
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""UserPromptSubmit hook: detect off-topic prompts and change-propagation triggers.
|
|
3
|
+
|
|
4
|
+
Two soft-reminder modes, both active only when a planning session is in
|
|
5
|
+
progress (signalled by a `.product-playbook-progress.md` whose status is
|
|
6
|
+
not "complete"):
|
|
7
|
+
|
|
8
|
+
1. Off-topic detection — if the user prompt contains debug / error /
|
|
9
|
+
"explain this code" keywords, remind Claude to follow the off-topic
|
|
10
|
+
handling rule documented in SKILL.md (save progress before answering,
|
|
11
|
+
then offer the continue/pause/end menu).
|
|
12
|
+
|
|
13
|
+
2. Change-propagation trigger — if the prompt contains keywords that
|
|
14
|
+
suggest revising an earlier step (e.g. "改 step 2", "update persona",
|
|
15
|
+
"重做 JTBD"), remind Claude to apply
|
|
16
|
+
`references/rules-change-propagation.md` so downstream artifacts stay
|
|
17
|
+
consistent.
|
|
18
|
+
|
|
19
|
+
Both reminders are advisory: the hook never blocks the user prompt. It
|
|
20
|
+
emits a JSON `systemMessage` that surfaces in Claude's context.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import json
|
|
26
|
+
import os
|
|
27
|
+
import re
|
|
28
|
+
import sys
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
|
|
31
|
+
OFF_TOPIC_PATTERNS = [
|
|
32
|
+
r"\bdebug\b",
|
|
33
|
+
r"\berror\b",
|
|
34
|
+
r"\bstack[\s\-]?trace\b",
|
|
35
|
+
r"\bexception\b",
|
|
36
|
+
r"\btraceback\b",
|
|
37
|
+
r"報錯",
|
|
38
|
+
r"錯誤訊息",
|
|
39
|
+
r"報錯訊息",
|
|
40
|
+
r"为什么这段",
|
|
41
|
+
r"為什麼這段",
|
|
42
|
+
r"幫我寫.*(code|程式|代碼)",
|
|
43
|
+
r"幫我改.*(code|程式|代碼)",
|
|
44
|
+
r"why does this code",
|
|
45
|
+
r"fix this (bug|code|function)",
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
CHANGE_PROPAGATION_PATTERNS = [
|
|
49
|
+
r"改\s*(step|步驟|S\d)",
|
|
50
|
+
r"重做\s*(step|步驟|S\d|JTBD|Persona|MVP|North\s*Star)",
|
|
51
|
+
r"重新\s*(做|寫|算).*(step|步驟|JTBD|Persona|MVP)",
|
|
52
|
+
r"修改.*(step|步驟|JTBD|Persona|MVP|North\s*Star)",
|
|
53
|
+
r"\bupdate\s+(step|persona|jtbd|mvp|north\s*star)",
|
|
54
|
+
r"\brewrite\s+(step|persona|jtbd|mvp)",
|
|
55
|
+
r"\bredo\s+(step|persona|jtbd|mvp)",
|
|
56
|
+
r"\bchange\s+(step|persona|jtbd|mvp|north\s*star)",
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _planning_in_progress(progress_file: Path) -> bool:
|
|
61
|
+
if not progress_file.is_file():
|
|
62
|
+
return False
|
|
63
|
+
try:
|
|
64
|
+
body = progress_file.read_text(encoding="utf-8", errors="ignore")
|
|
65
|
+
except OSError:
|
|
66
|
+
return False
|
|
67
|
+
lowered = body.lower()
|
|
68
|
+
if "status: complete" in lowered or "status:complete" in lowered:
|
|
69
|
+
return False
|
|
70
|
+
if re.search(r"status\s*[::]\s*completed", lowered):
|
|
71
|
+
return False
|
|
72
|
+
return True
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _matches_any(prompt: str, patterns: list[str]) -> bool:
|
|
76
|
+
return any(re.search(p, prompt, re.IGNORECASE) for p in patterns)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def main() -> int:
|
|
80
|
+
try:
|
|
81
|
+
payload = json.load(sys.stdin)
|
|
82
|
+
except (json.JSONDecodeError, ValueError):
|
|
83
|
+
return 0
|
|
84
|
+
|
|
85
|
+
prompt = (payload.get("user_prompt") or "").strip()
|
|
86
|
+
if not prompt:
|
|
87
|
+
return 0
|
|
88
|
+
|
|
89
|
+
cwd = payload.get("cwd") or os.getcwd()
|
|
90
|
+
progress = Path(cwd) / ".product-playbook-progress.md"
|
|
91
|
+
if not _planning_in_progress(progress):
|
|
92
|
+
return 0
|
|
93
|
+
|
|
94
|
+
reminders: list[str] = []
|
|
95
|
+
|
|
96
|
+
if _matches_any(prompt, OFF_TOPIC_PATTERNS):
|
|
97
|
+
reminders.append(
|
|
98
|
+
"[product-playbook] Off-topic prompt detected during an active "
|
|
99
|
+
"planning session. Before answering, follow the rule in "
|
|
100
|
+
"SKILL.md > 'Off-topic Prompt Handling': (1) save progress to "
|
|
101
|
+
"`.product-playbook-progress.md` per `references/rules-progress.md`, "
|
|
102
|
+
"(2) answer the question, (3) append the Continue / Pause / End "
|
|
103
|
+
"menu so the user can return to the flow."
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
if _matches_any(prompt, CHANGE_PROPAGATION_PATTERNS):
|
|
107
|
+
reminders.append(
|
|
108
|
+
"[product-playbook] Change intent detected. Apply "
|
|
109
|
+
"`references/rules-change-propagation.md`: identify which "
|
|
110
|
+
"downstream tables depend on the modified step, update them in "
|
|
111
|
+
"lock-step, and surface the propagation summary to the user."
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
if not reminders:
|
|
115
|
+
return 0
|
|
116
|
+
|
|
117
|
+
output = {"systemMessage": "\n\n".join(reminders)}
|
|
118
|
+
json.dump(output, sys.stdout)
|
|
119
|
+
return 0
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
if __name__ == "__main__":
|
|
123
|
+
sys.exit(main())
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "product-playbook",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "MUST use when user wants to plan or strategize a product/feature. 22 PM frameworks, 6 modes, from idea to dev handoff",
|
|
5
5
|
"bin": {
|
|
6
6
|
"product-playbook": "./install.sh"
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"install.sh",
|
|
32
32
|
"SKILL.md",
|
|
33
33
|
"commands/",
|
|
34
|
+
"hooks/",
|
|
34
35
|
"references/",
|
|
35
36
|
"i18n/",
|
|
36
37
|
"README.zh-TW.md",
|