claude-dev-env 1.41.0 → 1.43.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 (214) hide show
  1. package/CLAUDE.md +8 -0
  2. package/_shared/pr-loop/scripts/_claude_permissions_common.py +232 -8
  3. package/_shared/pr-loop/scripts/code_rules_gate.py +293 -8
  4. package/_shared/pr-loop/scripts/fix_hookspath.py +96 -5
  5. package/_shared/pr-loop/scripts/grant_project_claude_permissions.py +124 -20
  6. package/_shared/pr-loop/scripts/post_audit_thread.py +4 -4
  7. package/_shared/pr-loop/scripts/pr_loop_shared_constants/claude_permissions_constants.py +90 -0
  8. package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/claude_settings_keys_constants.py +2 -0
  9. package/_shared/pr-loop/scripts/preflight.py +13 -31
  10. package/_shared/pr-loop/scripts/reviews_disabled.py +2 -16
  11. package/_shared/pr-loop/scripts/revoke_project_claude_permissions.py +76 -33
  12. package/_shared/pr-loop/scripts/tests/conftest.py +1 -51
  13. package/_shared/pr-loop/scripts/tests/test_agent_config_carveout.py +385 -0
  14. package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +4 -2
  15. package/_shared/pr-loop/scripts/tests/test_claude_permissions_constants.py +37 -2
  16. package/_shared/pr-loop/scripts/tests/test_claude_settings_keys_constants.py +4 -2
  17. package/_shared/pr-loop/scripts/tests/test_code_rules_gate_constants.py +4 -2
  18. package/_shared/pr-loop/scripts/tests/test_fix_hookspath_constants.py +6 -2
  19. package/_shared/pr-loop/scripts/tests/test_grant_project_claude_permissions.py +2 -2
  20. package/_shared/pr-loop/scripts/tests/test_post_audit_thread.py +1 -2
  21. package/_shared/pr-loop/scripts/tests/test_post_audit_thread_constants.py +4 -2
  22. package/_shared/pr-loop/scripts/tests/test_preflight.py +17 -52
  23. package/_shared/pr-loop/scripts/tests/test_preflight_constants.py +6 -2
  24. package/_shared/pr-loop/scripts/tests/test_revoke_project_claude_permissions.py +5 -3
  25. package/agents/pr-description-writer.md +50 -140
  26. package/docs/PR_DESCRIPTION_GUIDE.md +101 -102
  27. package/hooks/_gh_pr_author_swap_utils.py +1 -1
  28. package/hooks/blocking/bot_mention_comment_blocker.py +4 -10
  29. package/hooks/blocking/code_rules_enforcer.py +217 -99
  30. package/hooks/blocking/code_rules_path_utils.py +8 -1
  31. package/hooks/blocking/destructive_command_blocker.py +1 -1
  32. package/hooks/blocking/es_exe_path_rewriter.py +7 -13
  33. package/hooks/blocking/gh_body_arg_blocker.py +6 -1
  34. package/hooks/blocking/gh_pr_author_enforcer.py +5 -5
  35. package/hooks/blocking/gh_pr_author_restore.py +5 -5
  36. package/hooks/blocking/hedging_language_blocker.py +4 -10
  37. package/hooks/blocking/md_path_exemptions.py +205 -0
  38. package/hooks/blocking/md_to_html_blocker.py +48 -20
  39. package/hooks/blocking/pr_converge_bugteam_enforcer.py +5 -11
  40. package/hooks/blocking/pr_description_enforcer.py +626 -41
  41. package/hooks/blocking/question_to_user_enforcer.py +4 -10
  42. package/hooks/blocking/state_description_blocker.py +6 -12
  43. package/hooks/blocking/tdd_enforcer.py +1 -1
  44. package/hooks/blocking/test_bot_mention_comment_blocker.py +1 -1
  45. package/hooks/blocking/test_code_rules_enforcer.py +3 -3
  46. package/hooks/blocking/test_code_rules_enforcer_any_exempt_files.py +1 -1
  47. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +0 -2
  48. package/hooks/blocking/test_code_rules_enforcer_comment_string_awareness.py +184 -0
  49. package/hooks/blocking/test_code_rules_enforcer_type_checking_scope.py +82 -0
  50. package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +29 -29
  51. package/hooks/blocking/test_gh_body_arg_blocker.py +7 -8
  52. package/hooks/blocking/test_gh_pr_author_enforcer.py +1 -1
  53. package/hooks/blocking/test_gh_pr_author_restore.py +1 -1
  54. package/hooks/blocking/test_hedging_language_blocker.py +2 -2
  55. package/hooks/blocking/test_md_to_html_blocker.py +463 -8
  56. package/hooks/blocking/test_pr_converge_bugteam_enforcer.py +1 -1
  57. package/hooks/blocking/test_pr_description_enforcer.py +1210 -13
  58. package/hooks/blocking/test_question_to_user_enforcer.py +1 -1
  59. package/hooks/blocking/windows_rmtree_blocker.py +5 -11
  60. package/hooks/diagnostic/hook_log_extractor.py +1 -1
  61. package/hooks/diagnostic/hook_log_init.py +1 -1
  62. package/hooks/diagnostic/hook_log_stop_wrapper.py +1 -1
  63. package/hooks/diagnostic/test_hook_log_extractor.py +1 -1
  64. package/hooks/diagnostic/test_hook_log_init.py +2 -2
  65. package/hooks/diagnostic/test_hook_log_stop_wrapper.py +1 -1
  66. package/hooks/git-hooks/gate_utils.py +1 -1
  67. package/hooks/git-hooks/pre_commit.py +1 -1
  68. package/hooks/git-hooks/pre_push.py +1 -1
  69. package/hooks/git-hooks/test_config.py +5 -5
  70. package/hooks/git-hooks/test_pre_push.py +6 -6
  71. package/hooks/{config → hooks_constants}/code_rules_enforcer_constants.py +37 -0
  72. package/hooks/hooks_constants/code_rules_path_utils_constants.py +28 -0
  73. package/hooks/hooks_constants/md_to_html_blocker_constants.py +82 -0
  74. package/hooks/{config → hooks_constants}/pr_converge_bugteam_enforcer_state.py +1 -1
  75. package/hooks/hooks_constants/pr_description_enforcer_constants.py +154 -0
  76. package/hooks/{config → hooks_constants}/pre_tool_use_stdin.py +1 -1
  77. package/hooks/{config → hooks_constants}/project_paths_reader.py +2 -2
  78. package/hooks/{config → hooks_constants}/test_banned_identifiers_constants.py +1 -1
  79. package/hooks/{config → hooks_constants}/test_dynamic_stderr_handler.py +1 -1
  80. package/hooks/{config → hooks_constants}/test_hardcoded_user_path_constants.py +1 -1
  81. package/hooks/{config → hooks_constants}/test_hook_log_extractor_constants.py +2 -2
  82. package/hooks/hooks_constants/test_md_to_html_blocker_constants.py +110 -0
  83. package/hooks/{config → hooks_constants}/test_messages.py +2 -6
  84. package/hooks/{config → hooks_constants}/test_path_rewriter_constants.py +1 -1
  85. package/hooks/hooks_constants/test_pr_description_enforcer_constants.py +292 -0
  86. package/hooks/{config → hooks_constants}/test_pre_tool_use_stdin.py +2 -2
  87. package/hooks/{config → hooks_constants}/test_project_paths_reader.py +3 -3
  88. package/hooks/{config → hooks_constants}/test_session_env_cleanup_constants.py +1 -1
  89. package/hooks/{config → hooks_constants}/test_setup_project_paths_constants.py +2 -2
  90. package/hooks/{config → hooks_constants}/test_unused_module_import_constants.py +1 -1
  91. package/hooks/lifecycle/pr_converge_bugteam_skill_tracker.py +5 -11
  92. package/hooks/lifecycle/test_pr_converge_bugteam_skill_tracker.py +1 -1
  93. package/hooks/session/gh_pr_author_session_cleanup.py +5 -6
  94. package/hooks/session/session_env_cleanup.py +4 -10
  95. package/hooks/session/test_gh_pr_author_session_cleanup.py +1 -1
  96. package/hooks/session/test_untracked_repo_detector.py +2 -2
  97. package/hooks/session/untracked_repo_detector.py +6 -12
  98. package/hooks/test__gh_pr_author_swap_utils.py +1 -1
  99. package/hooks/validators/run_all_validators.py +16 -5
  100. package/hooks/validators/test_output_formatter.py +46 -0
  101. package/hooks/workflow/doc_gist_auto_publish.py +1 -1
  102. package/hooks/workflow/md_to_html_companion.py +8 -15
  103. package/hooks/workflow/test_md_to_html_companion.py +184 -23
  104. package/package.json +1 -1
  105. package/rules/ask-user-question-required.md +1 -1
  106. package/rules/vault-context.md +1 -1
  107. package/scripts/{config → dev_env_scripts_constants}/timing.py +1 -1
  108. package/scripts/setup_project_paths.py +49 -11
  109. package/scripts/sweep_empty_dirs.py +10 -1
  110. package/scripts/test_setup_project_paths.py +2 -2
  111. package/scripts/test_sweep_empty_dirs.py +2 -6
  112. package/skills/_shared/pr-loop/scripts/_path_resolver.py +1 -1
  113. package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +1 -1
  114. package/skills/_shared/pr-loop/scripts/build_fix_prompt.py +1 -1
  115. package/skills/_shared/pr-loop/scripts/init_loop_state.py +1 -1
  116. package/skills/_shared/pr-loop/scripts/teardown_worktrees.py +1 -1
  117. package/skills/_shared/pr-loop/scripts/write_audit_outcomes.py +2 -2
  118. package/skills/_shared/pr-loop/scripts/write_fix_outcomes.py +2 -2
  119. package/skills/bugteam/PROMPTS.md +1 -1
  120. package/skills/bugteam/SKILL.md +1 -1
  121. package/skills/bugteam/reference/github-pr-reviews.md +1 -1
  122. package/skills/bugteam/scripts/{_claude_permissions_common.py → _bugteam_permissions_common.py} +110 -13
  123. package/skills/bugteam/scripts/bugteam_code_rules_gate.py +1 -13
  124. package/skills/bugteam/scripts/bugteam_fix_hookspath.py +1 -16
  125. package/skills/bugteam/scripts/bugteam_preflight.py +1 -13
  126. package/skills/bugteam/scripts/bugteam_scripts_constants/claude_permissions_common_constants.py +69 -0
  127. package/skills/bugteam/scripts/grant_project_claude_permissions.py +117 -12
  128. package/skills/bugteam/scripts/probe_code_rules_enforcer_check.py +1 -1
  129. package/skills/bugteam/scripts/reflow_skill_md.py +1 -1
  130. package/skills/bugteam/scripts/revoke_project_claude_permissions.py +71 -25
  131. package/skills/bugteam/scripts/{test__claude_permissions_common.py → test__bugteam_permissions_common.py} +4 -4
  132. package/skills/bugteam/scripts/test_agent_config_carveout.py +356 -0
  133. package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +0 -26
  134. package/skills/bugteam/scripts/{test_claude_permissions_common.py → test_bugteam_permissions_common.py} +3 -66
  135. package/skills/bugteam/scripts/test_bugteam_preflight.py +2 -27
  136. package/skills/bugteam/scripts/windows_safe_rmtree.py +1 -1
  137. package/skills/doc-gist/SKILL.md +1 -1
  138. package/skills/doc-gist/scripts/gist_upload.py +1 -1
  139. package/skills/implement/SKILL.md +66 -0
  140. package/skills/implement/scripts/append_note.py +133 -0
  141. package/skills/implement/scripts/implement_scripts_constants/__init__.py +0 -0
  142. package/skills/implement/scripts/implement_scripts_constants/notes_constants.py +12 -0
  143. package/skills/implement/scripts/test_append_note.py +191 -0
  144. package/skills/pr-converge/pr_converge_skill_constants/__init__.py +0 -0
  145. package/skills/pr-converge/{config → pr_converge_skill_constants}/constants.py +6 -1
  146. package/skills/pr-converge/scripts/check_bugbot_ci.py +2 -2
  147. package/skills/pr-converge/scripts/check_convergence.py +175 -29
  148. package/skills/pr-converge/scripts/check_pending_reviews.py +2 -2
  149. package/skills/pr-converge/scripts/fetch_copilot_reviews.py +2 -2
  150. package/skills/pr-converge/scripts/post_fix_reply.py +2 -2
  151. package/skills/pr-converge/scripts/pr_converge_scripts_constants/__init__.py +0 -0
  152. package/skills/pr-converge/scripts/{config → pr_converge_scripts_constants}/pr_converge_constants.py +1 -1
  153. package/skills/pr-converge/scripts/reflow_skill_md.py +90 -16
  154. package/skills/pr-converge/scripts/test_check_bugbot_ci.py +1 -1
  155. package/skills/pr-converge/scripts/test_check_convergence.py +324 -0
  156. package/skills/pr-converge/scripts/test_reflow_skill_md.py +0 -31
  157. package/skills/refine/SKILL.md +257 -0
  158. package/skills/refine/templates/implementation-notes-template.html +56 -0
  159. package/skills/refine/templates/plan-template.md +60 -0
  160. package/skills/session-log/SKILL.md +98 -233
  161. package/_shared/pr-loop/scripts/config/claude_permissions_constants.py +0 -36
  162. package/hooks/config/pr_description_enforcer_constants.py +0 -19
  163. package/hooks/config/test_pr_description_enforcer_constants.py +0 -82
  164. package/skills/bugteam/scripts/config/claude_permissions_common_constants.py +0 -20
  165. package/skills/bugteam/scripts/test_grant_project_claude_permissions.py +0 -55
  166. package/skills/bugteam/scripts/test_revoke_project_claude_permissions.py +0 -55
  167. package/skills/pr-converge/scripts/evict_cached_config_modules.py +0 -20
  168. package/skills/pr-converge/scripts/test_evict_cached_config_modules.py +0 -22
  169. /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/__init__.py +0 -0
  170. /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/code_rules_gate_constants.py +0 -0
  171. /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/fix_hookspath_constants.py +0 -0
  172. /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/post_audit_thread_constants.py +0 -0
  173. /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/preflight_constants.py +0 -0
  174. /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/reviews_disabled_constants.py +0 -0
  175. /package/hooks/git-hooks/{config.py → git_hooks_constants/__init__.py} +0 -0
  176. /package/hooks/{config → hooks_constants}/__init__.py +0 -0
  177. /package/hooks/{config → hooks_constants}/any_type_config.py +0 -0
  178. /package/hooks/{config → hooks_constants}/banned_identifiers_constants.py +0 -0
  179. /package/hooks/{config → hooks_constants}/blocking_check_limits.py +0 -0
  180. /package/hooks/{config → hooks_constants}/bot_mention_comment_blocker_constants.py +0 -0
  181. /package/hooks/{config → hooks_constants}/convergence_branch_constants.py +0 -0
  182. /package/hooks/{config → hooks_constants}/doc_gist_auto_publish_constants.py +0 -0
  183. /package/hooks/{config → hooks_constants}/dynamic_stderr_handler.py +0 -0
  184. /package/hooks/{config → hooks_constants}/gh_pr_author_swap_constants.py +0 -0
  185. /package/hooks/{config → hooks_constants}/hardcoded_user_path_constants.py +0 -0
  186. /package/hooks/{config → hooks_constants}/hook_log_extractor_constants.py +0 -0
  187. /package/hooks/{config → hooks_constants}/html_companion_constants.py +0 -0
  188. /package/hooks/{config → hooks_constants}/inline_tuple_string_magic_constants.py +0 -0
  189. /package/hooks/{config → hooks_constants}/messages.py +0 -0
  190. /package/hooks/{config → hooks_constants}/path_rewriter_constants.py +0 -0
  191. /package/hooks/{config → hooks_constants}/pr_converge_bugteam_enforcer_constants.py +0 -0
  192. /package/hooks/{config → hooks_constants}/session_env_cleanup_constants.py +0 -0
  193. /package/hooks/{config → hooks_constants}/setup_project_paths_constants.py +0 -0
  194. /package/hooks/{config → hooks_constants}/state_description_blocker_constants.py +0 -0
  195. /package/hooks/{config → hooks_constants}/stuttering_check_config.py +0 -0
  196. /package/hooks/{config → hooks_constants}/stuttering_import_binding_constants.py +0 -0
  197. /package/hooks/{config → hooks_constants}/sys_path_insert_constants.py +0 -0
  198. /package/hooks/{config → hooks_constants}/unused_module_import_constants.py +0 -0
  199. /package/hooks/{config → hooks_constants}/windows_rmtree_blocker_constants.py +0 -0
  200. /package/{skills/_shared/pr-loop/scripts/config → hooks/lifecycle}/__init__.py +0 -0
  201. /package/{skills/bugteam/scripts/config → hooks/session}/__init__.py +0 -0
  202. /package/scripts/{config → dev_env_scripts_constants}/__init__.py +0 -0
  203. /package/skills/{doc-gist/scripts/config → _shared/pr-loop/scripts/skills_pr_loop_constants}/__init__.py +0 -0
  204. /package/skills/_shared/pr-loop/scripts/{config → skills_pr_loop_constants}/path_resolver_constants.py +0 -0
  205. /package/skills/{pr-converge/config → bugteam/scripts/bugteam_scripts_constants}/__init__.py +0 -0
  206. /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/bugteam_code_rules_gate_constants.py +0 -0
  207. /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/bugteam_fix_hookspath_constants.py +0 -0
  208. /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/bugteam_preflight_constants.py +0 -0
  209. /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/probe_code_rules_enforcer_check_constants.py +0 -0
  210. /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/reflow_skill_md_constants.py +0 -0
  211. /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/windows_safe_rmtree_constants.py +0 -0
  212. /package/skills/{pr-converge/scripts/config → doc-gist/scripts/doc_gist_scripts_constants}/__init__.py +0 -0
  213. /package/skills/doc-gist/scripts/{config → doc_gist_scripts_constants}/gist_upload_constants.py +0 -0
  214. /package/skills/pr-converge/scripts/{config → pr_converge_scripts_constants}/reflow_skill_md_constants.py +0 -0
@@ -1,84 +1,76 @@
1
1
  ---
2
2
  name: session-log
3
3
  description: >-
4
- Log a session report as a styled HTML file in the vault, track vault context usage, extract unrecorded decisions, tidy the project's session folder, publish via /doc-gist as a shareable webpage, and output a /rename command. Use when the user says /session-log, journal this session, log this work, session report, or any variation of "summarize/log/record this session". Also triggers on "save session", "capture session", or "document what we did".
4
+ Log a session report by handing the HTML authorship to doc-gist (which designs fresh per session and auto-publishes via the `<!-- @publish-as-gist -->` marker hook), then track vault context, extract unrecorded decisions, tidy the project's session folder, and output a /rename command. Use when the user says /session-log, journal this session, log this work, session report, or any variation of "summarize/log/record this session". Also triggers on "save session", "capture session", or "document what we did".
5
5
  ---
6
6
 
7
7
  # Session Log
8
8
 
9
9
  ## Overview
10
10
 
11
- Write a structured session report as HTML, then run vault context tracking, decision extraction, session tidying, publish via /doc-gist, and finalize with a /rename clipboard hand-off.
11
+ The HTML artifact is doc-gist's job end-to-end. Session-log owns everything around it: where the file lives in the vault, what number it gets, the frontmatter metadata contract, post-write vault tracking, decision extraction, project-folder hygiene, and the closing `/rename` hand-off.
12
12
 
13
13
  **Announce at start:** "I'm logging this session."
14
14
 
15
- This skill runs as a 6-step workflow. Every step runs automatically -- no user prompts between steps except where noted.
15
+ ## Why this skill delegates HTML to doc-gist
16
16
 
17
- ## Gotchas
17
+ Sessions come in many shapes — convergence loops, feature builds, research dives, incidents, refactors, decisions. A single h2-emoji-list template forces every session into the same form regardless of fit, and the artifact reads as a process log rather than a substance log. Doc-gist's principle: *"design fresh per request, drawing on a gallery of 20 HTML shape patterns."* Each session report gets the shape that fits the session.
18
18
 
19
- - **HTML output, not Markdown.** The repo's `md_to_html_blocker` PreToolUse hook rejects Write/Edit on `.md` files outside `.claude/` directories. Headless vault paths (e.g., `$OBSIDIAN_VAULT_PATH`) resolve outside `.claude/`, so session reports use HTML. (The local vault at `~/.claude/vault/` is exempt, but HTML is the uniform format regardless of backend.)
20
- - **Avoid historical and comparative language.** The `state_description_blocker` hook enforces this for markdown and code files. While the hook does not scan `.html`, the same present-tense, current-state style applies to session reports. The trigger pattern set lives in `~/.claude/rules/no-historical-clutter.md`.
21
- - **doc-gist uses `--description` for gist title text.** `gist_upload.py` uploads HTML verbatim with no content parsing. Pass `--description "Session [N] — [Title]"` to set the gist description.
22
- - **doc-gist preview URL takes a few seconds.** The htmlpreview.github.io renderer fetches the raw gist on first hit. Quote both URLs and tell the user to refresh once if the page is blank.
23
- - **gh must be authenticated.** Running gist_upload.py with `gh` unauthenticated prints the auth prompt and exits non-zero. Surface that message to the user; the local HTML file in the vault is still the canonical artifact, so Step 6 still runs.
24
- - **Obsidian frontmatter index is sacrificed.** Obsidian's native YAML-frontmatter parser reads only `.md` files. HTML files do not appear in the Obsidian UI's frontmatter index. Search by content still works; search by `type: session-report` does not.
25
- - **Existing files require the Edit tool.** The `write_existing_file_blocker` hook rejects the Write tool on existing paths. Use Write only when creating a fresh session report; use Edit for Step 2's append and Step 4's auto-fixes.
19
+ The gallery lives at `~/.claude/skills/doc-gist/references/examples/`. Patterns that usually fit session reports:
26
20
 
27
- ## Backend Detection (run before Step 1)
21
+ | Session character | Gallery shape to study |
22
+ |---|---|
23
+ | Feature build / PR ships | `17-pr-writeup.html` |
24
+ | Incident, debugging arc, convergence loop | `12-incident-report.html` |
25
+ | Status update / weekly progress | `11-status-report.html` |
26
+ | Implementation plan or decision record | `16-implementation-plan.html` |
27
+ | Exploration of multiple approaches | `01-exploration-code-approaches.html` |
28
+ | Code-explainer with module map | `04-code-understanding.html` |
28
29
 
29
- Determine which storage backend is available. Try in this order and use the first that succeeds:
30
+ The session designer reads the matching gallery file, then designs the report in that shape. **Adapt, do not copy.**
30
31
 
31
- 1. **Headless vault** -- run `ob --version` via Bash to verify the obsidian-headless CLI is installed. Then check `OBSIDIAN_VAULT_PATH` environment variable or `~/.claude/vault/` for a vault directory. If the CLI exists and a vault directory resolves, optionally run `ob sync-status --path <vault-path>` to verify sync is active. Set `backend = "headless"`.
32
+ ## Gotchas
32
33
 
33
- 2. **Local vault** -- fall back to `~/.claude/vault/` as a local vault directory. Create it via `mkdir -p ~/.claude/vault/sessions` if it does not exist. Set `backend = "local"`. This provides a working vault structure that can be upgraded to headless sync later.
34
+ - **Doc-gist's auto-publish hook fires on Write/Edit of any HTML containing `<!-- @publish-as-gist -->`.** Session-log composes the HTML with the marker so auto-publish runs by default sessions are intended for sharing with collaborators. The hook prints the gist + preview URLs to tool output; capture both.
35
+ - **`gh` must be authenticated.** Auto-publish runs `gh gist create`. If `gh` is unauthenticated, `gh gist create` writes its error message; `gist_upload.py` surfaces it from stderr when present and falls back to stdout when stderr is empty (so the error always appears somewhere in the tool result). The hook exits 0 (does not block the Write). Look at the full tool result, then surface the error to the user; the local vault HTML is still the canonical artifact, so the remaining steps still run.
36
+ - **Vault paths sit outside `.claude/`.** Headless vault paths (e.g., `$OBSIDIAN_VAULT_PATH`) resolve outside the project tree. The `md_to_html_blocker` PreToolUse hook blocks `.md` writes unless the path is exempt — exemptions include any path containing `/.claude/` and README/CHANGELOG files at repo root. Session reports use HTML, which the hook ignores entirely.
37
+ - **Sessions describe current state by convention.** The state_description_blocker hook does not scan .html, but the rule at `~/.claude/rules/no-historical-clutter.md` applies as a writing standard — skip historical and comparative language when composing the report; the rule file lists the full trigger set.
38
+ - **`write_existing_file_blocker` rejects Write on existing paths.** Use Write only when creating a fresh session report; use Edit for the vault-context append in step 3.
39
+ - **Each Write/Edit of the marked HTML creates a fresh gist with a new ID.** `gist_upload.py` calls `gh gist create` with no lookup of any prior gist. Step 3 performs two Edits and Step 5's tidy may Edit the current session a third time — each Edit produces a different gist URL than its predecessor. Quote the URLs from the FINAL publish (the most recent auto-publish run for the session being created — the second Step-3 Edit when Step 5 does not touch the current session, or the Step 5 Edit when it does) to the user; never embed a URL inside the HTML that a later Edit then re-publishes.
40
+ - **Obsidian frontmatter index is HTML-blind.** Obsidian's native YAML-frontmatter parser reads only `.md` files. HTML files do not appear in Obsidian's frontmatter index. Search by content still works; search by `type: session-report` does not.
41
+ - **Auto-published gists carry the default `gist_upload.py` description (`HTML artifact`), not a session-specific title.** The auto-publish hook invokes `gist_upload.py` with only `--input` and `--no-open` — it has no session-title context to pass as `--description`. Browsing or sharing gists, operators see "HTML artifact" rather than `Session [N] — [Title] — session-log — [Project]`. Workaround: after the canonical Edit completes, edit the existing gist's description in place with `gh gist edit <gist-id> --desc "Session [N] — [Title] — session-log — [Project]"` (this updates the existing gist's metadata; no new gist ID is created) if a meaningful title matters for that share. The HTML frontmatter inside the file remains the authoritative session metadata regardless of the gist's external description.
42
+
43
+ ## Backend Detection (run before Step 1)
34
44
 
35
- **Backend capabilities:**
45
+ Determine which storage backend is available. First success wins.
36
46
 
37
- | Capability | headless | local |
38
- |---|---|---|
39
- | Write session reports | Write tool to `.html` path | Write tool to `.html` path |
40
- | Search prior sessions | Bash `ls` + Grep | Bash `ls` + Grep |
41
- | Session number detection | parse filenames | parse filenames |
42
- | Frontmatter | YAML inside HTML comment | YAML inside HTML comment |
43
- | Sync | Obsidian Sync | none (local only) |
47
+ 1. **Headless vault** — Bash `ob --version` to verify the obsidian-headless CLI is installed. Check `OBSIDIAN_VAULT_PATH` env var or `~/.claude/vault/` for a vault directory. When the CLI check succeeds AND at least one of those paths resolves to a vault directory, set `backend = "headless"`.
48
+ 2. **Local vault** — fall back to `~/.claude/vault/`. Create `~/.claude/vault/sessions` via `mkdir -p` if missing. Set `backend = "local"`.
44
49
 
45
- **Session number detection:**
46
- - List files in the project directory via Bash `ls`
47
- - Parse filenames matching `[N]. *.html` or `[N]. *.md` to preserve sequence across the format migration
48
- - Highest N + 1. If directory does not exist, create it and start at 1
50
+ **Session-number detection:** Bash `ls` the project's session folder, parse filenames matching `[N]. *.html` or `[N]. *.md` to preserve sequence across the format migration. Highest N + 1. New project → start at 1.
49
51
 
50
52
  **Output paths:**
51
53
  - headless: `$OBSIDIAN_VAULT_PATH/sessions/[Project]/[N]. [Title].html` (falls back to `~/.claude/vault/` when the env var is unset)
52
54
  - local: `~/.claude/vault/sessions/[Project]/[N]. [Title].html`
53
55
 
54
- Announce the backend: "Using headless vault at [path]." or "Using local vault at ~/.claude/vault/. Run `/obsidian-check` for upgrade options."
56
+ Announce the backend: "Using headless vault at [path]." or "Using local vault at ~/.claude/vault/. Install obsidian-headless and set the `OBSIDIAN_VAULT_PATH` environment variable (PowerShell: `$env:OBSIDIAN_VAULT_PATH = '<path>'`; POSIX shells: `export OBSIDIAN_VAULT_PATH=<path>`) to enable sync."
55
57
 
56
58
  ---
57
59
 
58
- ## Step 1: Write Session Report (HTML)
60
+ ## Step 1: Compose Session Metadata
59
61
 
60
- 1. Review the conversation to identify: key outcomes, blockers, decisions, and next steps.
62
+ Review the conversation to identify the session's primary outcome and the small set of facts a cold reader needs.
61
63
 
62
- 2. Determine session metadata:
63
- - **Project name:** infer from conversation context
64
- - **Session number:** list the project's vault folder via Bash `ls`, parse `[N]. *.html` and `[N]. *.md` filenames, take highest+1. If no prior sessions, start at 1.
65
- - **Date:** today's date
66
- - **Title:** a 2-5 word summary of the session's primary outcome or focus area. Pick the single most important thing that happened. Examples: "Amazon Auth Migration", "Source Loading Fix", "Vault Reorganization". Avoid generic titles like "Bug Fixes" or "Various Updates".
64
+ Resolve the metadata used by the frontmatter and the vault path:
67
65
 
68
- 3. **Write to the vault path** via the Write tool. Create the project directory via `mkdir -p` if it does not exist. If the write fails, output the content in the conversation so the user can copy it manually. Skip Steps 2-5 and go directly to Step 6.
66
+ - **Project name:** infer from conversation context
67
+ - **Session number:** from backend detection above
68
+ - **Date:** today's date
69
+ - **Title:** a 2–5 word summary of the session's primary outcome. Examples: "Amazon Auth Migration", "Source Loading Fix", "PR 475 Convergence". Avoid generic titles like "Bug Fixes".
69
70
 
70
- ### Vault Format -- HTML Session Report
71
-
72
- The vault note is a self-contained HTML file. Frontmatter lives inside an HTML comment so it is human-readable but does not affect rendering. Styling is minimal so doc-gist's template wraps the body cleanly in Step 5.
71
+ The frontmatter contract every session report carries (inside an HTML comment, as the first child of `<body>`):
73
72
 
74
73
  ```html
75
- <!DOCTYPE html>
76
- <html lang="en">
77
- <head>
78
- <meta charset="utf-8">
79
- <title>Session [N] — [Title]</title>
80
- </head>
81
- <body>
82
74
  <!--
83
75
  type: session-report
84
76
  project: [name]
@@ -86,253 +78,126 @@ session: [N]
86
78
  date: [YYYY-MM-DD]
87
79
  status: completed|in-progress|blocked
88
80
  blocked: true|false
89
- tags: [session, [project-tag]]
81
+ vault_context_retrieved: true|false
82
+ tags: [session, [project-tag], [topic-tags]]
90
83
  -->
91
-
92
- <h1>Session [N] Report — [Month Day, Year]</h1>
93
-
94
- <h2>[emoji] [Section Title]</h2>
95
- <p>[1-3 sentence explanation of what happened and why it matters]</p>
96
- <ul>
97
- <li><strong>[Label]:</strong> [detail]</li>
98
- </ul>
99
- <p><strong>Fix:</strong> [what was done]</p>
100
-
101
- <h2>[emoji] [Section with tabular data]</h2>
102
- <p>[Context sentence]</p>
103
- <table>
104
- <thead><tr><th>#</th><th>Item</th><th>Status</th></tr></thead>
105
- <tbody>
106
- <tr><td>1</td><td>...</td><td>...</td></tr>
107
- </tbody>
108
- </table>
109
-
110
- <h2>[emoji] Notes</h2>
111
- <ul>
112
- <li><strong>[Topic]:</strong> [detail]</li>
113
- </ul>
114
-
115
- </body>
116
- </html>
117
84
  ```
118
85
 
119
- ### Emoji Status Indicators
86
+ Every session report carries this metadata block verbatim so vault search and the tidy step in step 5 work. **Initial values for Step 2's Write:** substitute concrete values for every placeholder — for `vault_context_retrieved`, write the literal value `false` (the safe default before Step 3's vault-MCP-tool scan completes). Step 3 then Edits this to `true` if any of the three vault MCP tools fired this session.
120
87
 
121
- | Emoji | Meaning | Use when |
122
- |-------|---------|----------|
123
- | ✅ | Done/Fixed | A problem was resolved or a deliverable completed |
124
- | 🚫 | Blocked | Something couldn't be done (external limit, dependency, etc.) |
125
- | ⚠️ | Warning/Note | Important context, gotchas, or things to remember |
126
- | 🔧 | In Progress | Work started but not finished |
127
- | 📋 | Queued | Work identified but not yet started |
88
+ ## Step 2: Compose the HTML via doc-gist's shape principles
128
89
 
129
- ### Formatting Rules
90
+ Design the artifact for **this session's character**, drawing on the doc-gist gallery patterns listed above. The report must answer for a cold reader, from the H2 headers alone, three questions: *what shipped*, *why it matters*, *what impact it had*. Process narration (commit-by-commit walks, agent gotchas, retry counts) belongs at the end, not in the opening sections.
130
91
 
131
- - **Section headers** (`<h2>`) get one emoji + descriptive title
132
- - **Explanatory paragraphs** (`<p>`) under each header -- not just bullets. Explain what happened and why.
133
- - **Bold inline labels** for key facts: `<strong>Fix:</strong>`, `<strong>Account:</strong>`, etc.
134
- - **Tables** (`<table>`) for anything with 3+ rows of structured data (queued items, test results, file lists)
135
- - **Bullets** (`<ul><li>`) for lists of 2+ related items
136
- - **Links** (`<a href="...">`) where useful: file paths, URLs, PR links
137
- - **No inline `style=` attributes and no `<style>` block.** Doc-gist wraps the body in its own template; inner styles fight the wrapper.
138
-
139
- ### What NOT to include
140
-
141
- - Play-by-play of debugging steps or failed approaches
142
- - Process narration ("First I tried X, then Y")
143
- - Redundant sections -- if nothing was blocked, skip the blocked section
144
- - Historical or comparative language — see `~/.claude/rules/no-historical-clutter.md` for the trigger pattern set. The `state_description_blocker` hook rejects writes containing these patterns in markdown/code; the same rule applies to session report HTML.
145
-
146
- ### Example
92
+ **Required somewhere in the HTML (commonly in `<head>`):** the auto-publish marker.
147
93
 
148
94
  ```html
149
- <!DOCTYPE html>
150
- <html lang="en">
151
- <head>
152
- <meta charset="utf-8">
153
- <title>Session 6 — Developer Docs Sources Fixed</title>
154
- </head>
155
- <body>
156
- <!--
157
- type: session-report
158
- project: claude-academy
159
- session: 6
160
- date: 2026-03-27
161
- status: completed
162
- blocked: false
163
- tags: [session, claude-academy]
164
- -->
95
+ <!-- @publish-as-gist -->
96
+ ```
165
97
 
166
- <h1>Session 6 ReportMarch 27, 2026</h1>
98
+ The marker triggers the PostToolUse hook after Write or Edit the hook scans the entire HTML for the literal sentinel string and uploads the file to a secret gist, then prints the gist + preview URLs to your tool output. The marker must be the literal comment text exactly; whitespace inside breaks it.
167
99
 
168
- <h2>✅ Developer Docs Sources Fixed</h2>
169
- <p>Both Developer Docs notebooks load fully with working sources:</p>
170
- <ul>
171
- <li><strong>Notebook #28 — Building with Claude &amp; Tools:</strong> 52 sources loaded (all green)</li>
172
- <li><strong>Notebook #29 — Agent SDK &amp; Testing:</strong> 49 sources loaded (all green)</li>
173
- </ul>
174
- <p><strong>Fix:</strong> Use <code>docs.anthropic.com/en/docs/X</code> URLs (drop the .md extension, swap domain). Tested one URL first, then bulk-loaded.</p>
100
+ **Required at the top of `<body>`:** the frontmatter HTML comment from step 1.
175
101
 
176
- <h2>🚫 Audio Generation Blocked</h2>
177
- <p>All 10 Audio Overviews are ready to generate but hit the daily limit wall.</p>
102
+ **Required as the first content section:** an opening "What this session shipped" paragraph + bullets — written so a reader with zero prior context understands the outcome. For continuation sessions (where the substantive work landed in a prior session), recap the parent session's outcome briefly so the report stands alone.
178
103
 
179
- <h2>⚠️ Session 7 Notes</h2>
180
- <ul>
181
- <li><strong>Account:</strong> Notebooks live under secondary@example.com (authuser=1), NOT the default primary@example.com.</li>
182
- <li><strong>Audio budget:</strong> Once the 24h window resets, all 10 overviews fit within the 20/day Pro limit.</li>
183
- </ul>
104
+ **Required: self-contained HTML.** Embed all styles in a `<style>` block inside `<head>` (inline `style="..."` attributes are also fine if preferred); no `./relative/paths` to local assets. Doc-gist's transport sends a single HTML file with no neighbors, so any relative-path reference (CSS, JS, images) cannot resolve and the asset will be missing from the preview. Absolute external URLs (CDN-hosted assets, image hotlinks) work but add an external dependency the report should not need — design the report to stand alone with no external dependencies.
184
105
 
185
- </body>
186
- </html>
187
- ```
106
+ Beyond those four requirements, design the shape that fits. A convergence loop session reads naturally as an incident timeline (`12-incident-report.html`); a feature build reads as a PR writeup (`17-pr-writeup.html`); a research session reads as a feature explainer (`14-research-feature-explainer.html`). Read the matching gallery entry for typography, palette, spatial idioms — adapt, do not copy.
188
107
 
189
- ---
108
+ **Write the file** via the Write tool to the vault path. Create the project directory via `mkdir -p` if it does not exist. The auto-publish hook fires after the Write completes and prints a gist + preview URL pair to tool output (the bare preview URL on stdout; both labeled `Gist: <url>` and `Preview: <url>` lines on stderr — both streams appear in the agent's tool-result). Step 3 performs **two Edit calls** and each one re-fires the hook with a fresh URL pair — only the URL pair from the second (final) Step-3 Edit is canonical for the just-created session (the step-2 pair and the first step-3 pair are orphaned the moment the next Edit lands). Always quote the URL pair from the **most recent auto-publish run for the session being created**. Note: Step 5's tidy audits every `.html` file in the project folder including the just-created session, so if Step 5's frontmatter auto-fix touches the current session, the Step 5 republish URL pair becomes the new canonical pair for that session (re-quote it to the user with a "Session republished — new preview: <url>" line). The second Step-3 Edit's URL pair stays canonical only when Step 5 does not touch the current session.
190
109
 
191
- ## Step 2: Vault Context Tracking
110
+ **If the Write fails**, output the HTML content in the conversation so the user can copy it manually. Before emitting the HTML to chat, resolve the `vault_context_retrieved` placeholder to `true` or `false` based on the same vault-MCP-tool scan that Step 3 would have run, and include the matching vault-context `<li>` line (Retrieved or Not retrieved) so the emitted HTML is a valid copy-paste artifact with complete frontmatter. Skip Step 3 and continue at step 4.
192
111
 
193
- This step runs automatically after Step 1 completes.
112
+ ## Step 3: Vault Context Tracking
194
113
 
195
- 1. **Check vault context usage.** Review the conversation history for any use of these MCP tools (excluding this skill's own calls during Step 1):
196
- - `mcp__obsidian__search_notes`
197
- - `mcp__obsidian__read_note`
198
- - `mcp__obsidian__read_multiple_notes`
114
+ This step runs automatically after step 2.
199
115
 
200
- Track whether vault context was used and which notes were read (if any). This determines the Notes line appended in step 2.
116
+ Review the conversation history for any use of these vault MCP tools (look only at tool calls the session made before /session-log itself ran):
201
117
 
202
- 2. **Append a tracking line to the Notes section** via the Edit tool. Target the closing `</ul>` of the last `<h2>...Notes</h2>` block:
203
- - If retrieved: `<li><strong>Vault context:</strong> Retrieved ([list of note paths])</li>`
204
- - If not retrieved: `<li><strong>Vault context:</strong> Not retrieved</li>`
118
+ - `mcp__obsidian__search_notes`
119
+ - `mcp__obsidian__read_note`
120
+ - `mcp__obsidian__read_multiple_notes`
205
121
 
206
- If no Notes section exists, append a fresh one before `</body>`:
207
- ```html
208
- <h2>⚠️ Notes</h2>
209
- <ul>
210
- <li><strong>Vault context:</strong> Not retrieved</li>
211
- </ul>
212
- ```
122
+ Edit the vault HTML via two Edit calls (each Edit re-fires the auto-publish hook and creates a fresh gist; **the URL pair from the second/final Edit is canonical** — the first Edit's URLs are orphaned the moment the second Edit lands):
213
123
 
214
- ---
124
+ 1. Set the frontmatter `vault_context_retrieved` field to `true` when any of the three tools fired this session, `false` otherwise.
125
+ 2. Append one fact — vault-context status — into whatever section the report designer placed for notes / metadata / references. If the report has no such section, append a fresh `<h2>Notes</h2>` block before `</body>`:
215
126
 
216
- ## Step 3: Decision Extraction
127
+ ```html
128
+ <h2>Notes</h2>
129
+ <ul>
130
+ <!-- Pick exactly one of the two forms based on whether vault MCP tools fired this session: -->
131
+ <li><strong>Vault context:</strong> Retrieved ([list of note paths])</li>
132
+ <li><strong>Vault context:</strong> Not retrieved</li>
133
+ </ul>
134
+ ```
217
135
 
218
- This step runs automatically after Step 2 completes.
136
+ If the report already has a notes / references section, use Edit to insert one matching child element at the end of that section. The element shape mirrors whatever the section already uses: an extra `<li>` before the closing `</ul>` for a list, an extra `<dt>Vault context</dt><dd>…</dd>` pair before the closing `</dl>` for a description list, an extra `<p><strong>Vault context:</strong> …</p>` before the section's closing tag for a paragraph-based section. Pick the form that matches the surrounding markup.
219
137
 
220
- Scan the conversation for decisions, gotchas, or architectural choices that were not already saved via `/remember` or to memory. For each one found, ask the user:
138
+ The gist URL stays out of the HTML body on purpose: each Edit re-fires the auto-publish hook and produces a brand-new gist ID, so any URL embedded in the file becomes stale the instant the next Edit lands. The canonical gist + preview URL is the pair from **the most recent auto-publish run that touched the current session report** — typically the second (final) Step-3 Edit, but the Step 5 Edit takes over as canonical when Step 5's frontmatter auto-fix edits the current session (re-quote the Step 5 pair with a "Session [N] republished — new preview: <url>" line per the Step 2 guidance at line 107). Quote whichever pair is most recent when announcing the report.
221
139
 
222
- > "I noticed this decision: [summary]. Want me to save it to the vault with /remember?"
140
+ ## Step 4: Decision Extraction
223
141
 
224
- Only write decision notes the user confirms. If no unrecorded decisions are found, skip silently.
142
+ Scan the conversation for decisions, gotchas, or architectural choices that were not already saved via `/remember`. For each one found, ask the user via `AskUserQuestion`:
225
143
 
226
- ---
144
+ > "I noticed this decision: [summary]. Save it to the vault via `/remember`?"
227
145
 
228
- ## Step 4: Session Tidy (Project Scope)
146
+ Only invoke `/remember` for decisions the user confirms; `/remember` writes the decision as a vault note. If no unrecorded decisions are found, skip silently.
229
147
 
230
- This step runs automatically after Step 3 completes. Scope: the current project's session folder only.
148
+ ## Step 5: Session Tidy (Project Scope)
231
149
 
232
- 1. **List files** in the project's vault session folder via Bash `ls`.
150
+ Scope: the current project's session folder only.
233
151
 
152
+ 1. **List files** in the project's vault session folder via Bash `ls`.
234
153
  2. **Quick audit** each `.html` file for:
235
154
  - **Naming convention:** must match `[N]. [Title].html`
236
- - **Frontmatter completeness:** HTML comment block at top contains `type`, `project`, `session`, `date`, `status`, `blocked`, `tags`
155
+ - **Frontmatter completeness:** HTML comment block at top of `<body>` contains `type`, `project`, `session`, `date`, `status`, `blocked`, `vault_context_retrieved`, `tags`
237
156
  - **Status coherence:** `status: completed` with `blocked: true` is contradictory. `status: in-progress` or `status: blocked` on sessions older than 7 days is stale.
238
-
239
- 3. **Auto-fix minor issues silently** via Edit tool:
240
- - Missing frontmatter fields that can be inferred (e.g., `blocked: false` when status is `completed`)
157
+ 3. **Auto-fix minor issues** via Edit:
158
+ - Missing frontmatter fields that can be inferred (e.g., `blocked: false` when status is `completed`; `vault_context_retrieved: false` when the field is absent, since the field defaults to false in pre-existing sessions)
241
159
  - `type` field set to a wrong value (correct to `session-report`)
242
160
 
161
+ Each Edit on a marked HTML file re-fires doc-gist's auto-publish hook and produces a fresh gist + preview URL pair in the agent's tool output (the bare preview URL on stdout; both labeled `Gist: <url>` and `Preview: <url>` lines on stderr) — the prior gist URL becomes orphaned. Surface a `Session [N] republished — new preview: <url>` line per Edit so the user can update any prior shares.
243
162
  4. **Report issues that need user input:**
244
163
  - Files with wrong naming convention (propose new name)
245
164
  - Stale statuses (propose update to `completed` or ask)
246
165
  - Contradictory status/blocked combos
247
166
 
248
167
  If no issues are found, skip silently. Do not report "all clean."
249
-
250
168
  5. **Rollup check:** if the project has 5+ sessions and no `Summary.html` or `Summary.md`, mention it:
251
- > "This project has [N] sessions and no summary. Run `/session-tidy` for a full rollup."
252
-
253
- ---
254
-
255
- ## Step 5: Publish via /doc-gist
256
-
257
- This step runs automatically after Step 4 completes.
258
-
259
- ### 5a. Run gist_upload.py
260
-
261
- Hand the freshly-written HTML file to `/doc-gist` via its gist_upload.py script. Use the PowerShell tool so quoting handles spaces in the vault path:
262
-
263
- ```powershell
264
- python "$HOME/.claude/skills/doc-gist/scripts/gist_upload.py" `
265
- --input "<absolute path to the .html file>" `
266
- --description "Session [N] — [Title] · session-log · [Project]"
267
- ```
268
-
269
- Replace the bracketed values from Step 1's metadata. The script writes a temp copy, uploads as a secret gist, and prints two URLs to stderr — capture both:
270
-
271
- - **Preview:** `https://htmlpreview.github.io/?https://gist.githubusercontent.com/...`
272
- - **Gist:** `https://gist.github.com/...`
273
-
274
- Quote both URLs back to the user as clickable links.
275
-
276
- **If gh is not authenticated**, gist_upload.py exits non-zero with the `gh auth login` prompt. Surface that message to the user, skip 5b, and continue with Step 6 — the vault HTML is the canonical artifact. The publish step is a hand-off, not a gate.
277
-
278
- **If the browser should not open automatically**, append `--no-open`. The gist still publishes; only the auto-open is suppressed.
169
+ > "This project has [N] sessions and no summary. A rollup would help; `/session-tidy` is defined for the Markdown session format and may mis-audit or propose destructive renames against HTML sessions, so a manual rollup is the safe path."
279
170
 
280
- ### 5b. Inject the gist URL back into the session log
281
-
282
- After 5a succeeds, edit the vault HTML to embed the Preview URL inside the Notes section so future readers of the local file can jump to the published gist. Use the Edit tool with the absolute path from Step 1.
171
+ ## Step 6: Finalize
283
172
 
284
- Target: the closing `</ul>` of the last `<h2>...Notes</h2>` block. Insert this line directly before it:
173
+ Copy a `/rename` command to the user's clipboard via PowerShell:
285
174
 
286
- ```html
287
- <li><strong>Published as:</strong> <a href="<preview URL from 5a>">gist preview</a></li>
288
175
  ```
289
-
290
- If the file has no Notes section, append a fresh one before `</body>`:
291
-
292
- ```html
293
- <h2>⚠️ Notes</h2>
294
- <ul>
295
- <li><strong>Published as:</strong> <a href="<preview URL from 5a>">gist preview</a></li>
296
- </ul>
176
+ pwsh -NoProfile -Command "Set-Clipboard '/rename [Project] - [Primary Outcome]'"
297
177
  ```
298
178
 
299
- The vault HTML now contains the gist URL inline. Subsequent re-publishes will overwrite the entry only if step 5b is rerun with the new URL — the safe path is to leave the first entry as-is unless the user explicitly asks for a re-publish.
300
-
301
- ---
302
-
303
- ## Step 6: Finalize
304
-
305
- Copy a `/rename` command to the user's clipboard via Bash: `echo -n "/rename [Project] - [Primary Outcome]" | clip.exe`. Then tell the user:
179
+ Then tell the user:
306
180
 
307
181
  > "Copied `/rename [Project] - [Primary Outcome]` to your clipboard. Paste it to rename this session."
308
182
 
309
- The primary outcome comes from the session title determined in Step 1.
183
+ The primary outcome comes from the session title resolved in step 1.
310
184
 
311
185
  ---
312
186
 
313
- ## Best Practices
314
-
315
- - Each section in the session report is an outcome, not a process step ("Sources Fixed" not "We debugged source loading")
316
- - Body should be self-contained -- no context needed beyond the note itself
317
- - If the session was exploratory with no concrete outcome, use 🔧 or 📋 sections to describe what was investigated and what's next
318
- - Keep it scannable: a reader should grasp the session in 15 seconds from headers alone
319
- - Tables are powerful -- use them whenever you have structured data (queued work, test results, file inventories)
320
- - Skip inline styling. Doc-gist's template provides the visual rhythm; semantic HTML (h1/h2/p/ul/table) carries the structure.
321
-
322
187
  ## Run-and-report checklist
323
188
 
324
- Copy and check off:
325
-
326
189
  - [ ] Backend detected and announced
327
- - [ ] Session number resolved from `[N]. *.html` files
328
- - [ ] HTML written to vault path (Write tool, fresh path)
329
- - [ ] Vault context line appended to Notes section (Edit tool)
190
+ - [ ] Session number resolved from `[N]. *.html` and `[N]. *.md` files (both parsed to preserve sequence across the format migration)
191
+ - [ ] HTML composed via doc-gist's shape principles (gallery-anchored)
192
+ - [ ] `<!-- @publish-as-gist -->` marker present somewhere in the HTML
193
+ - [ ] Frontmatter HTML comment present at top of `<body>`
194
+ - [ ] Opening section answers "what shipped / why / impact" for a cold reader
195
+ - [ ] Self-contained HTML (no relative-path asset refs; avoid external dependencies)
196
+ - [ ] Auto-publish URLs captured from step 2 and step 3 (or HTML emitted to chat when step 2 Write failed)
197
+ - [ ] Vault-context line appended via Edit (step 3); URL pair from the most recent auto-publish run for the current session quoted to the user (typically the final Step-3 Edit, but the Step 5 republish takes over when Step 5 edits the current session)
330
198
  - [ ] Decision extraction surfaced any unrecorded items
331
199
  - [ ] Session tidy reported anomalies or stayed silent
332
- - [ ] doc-gist publish script invoked with `--input` and `--description`
333
- - [ ] Preview URL and Gist URL quoted to the user
334
- - [ ] Preview URL injected back into the vault HTML's Notes section
335
- - [ ] /rename command copied to clipboard via `clip.exe`
200
+ - [ ] `/rename` command copied to clipboard via `pwsh Set-Clipboard`
336
201
 
337
202
  ## Folder map
338
203
 
@@ -1,36 +0,0 @@
1
- """Constants shared by grant_project_claude_permissions and revoke_project_claude_permissions."""
2
-
3
- from pathlib import Path
4
-
5
- from config.preflight_constants import GIT_DIRECTORY_NAME
6
-
7
- __all__ = (
8
- "ALL_PERMISSION_ALLOW_TOOLS",
9
- "AUTO_MODE_ENVIRONMENT_ENTRY_TEMPLATE",
10
- "CLAUDE_SETTINGS_DIRECTORY_NAME",
11
- "CLAUDE_SETTINGS_FILENAME",
12
- "GIT_DIRECTORY_NAME",
13
- "TEXT_FILE_ENCODING",
14
- "UNIQUE_TEMPORARY_SUFFIX_BYTE_LENGTH",
15
- "get_claude_user_settings_path",
16
- )
17
-
18
-
19
- ALL_PERMISSION_ALLOW_TOOLS: tuple[str, ...] = ("Edit", "Write", "Read")
20
-
21
- AUTO_MODE_ENVIRONMENT_ENTRY_TEMPLATE: str = (
22
- "Trusted local workspace: {project_path}/.claude/** is the user's "
23
- "project Claude Code config tree; edits inside are routine"
24
- )
25
-
26
- CLAUDE_SETTINGS_DIRECTORY_NAME: str = ".claude"
27
-
28
- CLAUDE_SETTINGS_FILENAME: str = "settings.json"
29
-
30
- TEXT_FILE_ENCODING: str = "utf-8"
31
-
32
- UNIQUE_TEMPORARY_SUFFIX_BYTE_LENGTH: int = 8
33
-
34
-
35
- def get_claude_user_settings_path() -> Path:
36
- return Path.home() / CLAUDE_SETTINGS_DIRECTORY_NAME / CLAUDE_SETTINGS_FILENAME
@@ -1,19 +0,0 @@
1
- """Configuration constants for the pr_description_enforcer PreToolUse hook."""
2
-
3
- import os
4
- import re
5
-
6
-
7
- _PLUGIN_ROOT: str = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
8
- PR_GUIDE_PATH: str = os.path.join(_PLUGIN_ROOT, "docs", "PR_DESCRIPTION_GUIDE.md")
9
-
10
- MINIMUM_SUBSTANTIVE_PROSE_CHARS: int = 40
11
-
12
- FENCED_CODE_BLOCK_PATTERN: re.Pattern[str] = re.compile(r"```.*?```", re.DOTALL)
13
- INLINE_CODE_PATTERN: re.Pattern[str] = re.compile(r"`[^`]*`")
14
- HEADING_LINE_PATTERN: re.Pattern[str] = re.compile(r"^#+[ \t].*$", re.MULTILINE)
15
- BOLD_PAIR_PATTERN: re.Pattern[str] = re.compile(r"\*\*([^*]+?)\*\*")
16
- BULLET_MARKER_PATTERN: re.Pattern[str] = re.compile(r"^\s*[-*+]\s+", re.MULTILINE)
17
- BLOCKQUOTE_MARKER_PATTERN: re.Pattern[str] = re.compile(r"^\s*>\s+", re.MULTILINE)
18
- LINK_TEXT_PATTERN: re.Pattern[str] = re.compile(r"\[([^\]]+)\]\([^)]+\)")
19
- WHITESPACE_RUN_PATTERN: re.Pattern[str] = re.compile(r"\s+")
@@ -1,82 +0,0 @@
1
- """Behavior tests for pr_description_enforcer_constants module."""
2
-
3
- from __future__ import annotations
4
-
5
- import os
6
- import sys
7
- from pathlib import Path
8
-
9
- _HOOKS_ROOT = Path(__file__).resolve().parent.parent
10
- if str(_HOOKS_ROOT) not in sys.path:
11
- sys.path.insert(0, str(_HOOKS_ROOT))
12
-
13
- from config import pr_description_enforcer_constants as constants_module
14
-
15
-
16
- def test_plugin_root_is_private_module_attribute() -> None:
17
- assert hasattr(constants_module, "_PLUGIN_ROOT")
18
- assert isinstance(constants_module._PLUGIN_ROOT, str)
19
- assert os.path.isabs(constants_module._PLUGIN_ROOT)
20
-
21
-
22
- def test_plugin_root_public_name_is_not_exported() -> None:
23
- assert not hasattr(constants_module, "PLUGIN_ROOT")
24
-
25
-
26
- def test_pr_guide_path_resolves_under_plugin_root_docs() -> None:
27
- expected_pr_guide_path = os.path.join(
28
- constants_module._PLUGIN_ROOT, "docs", "PR_DESCRIPTION_GUIDE.md"
29
- )
30
- assert constants_module.PR_GUIDE_PATH == expected_pr_guide_path
31
-
32
-
33
- def test_minimum_substantive_prose_chars_is_positive_integer() -> None:
34
- assert isinstance(constants_module.MINIMUM_SUBSTANTIVE_PROSE_CHARS, int)
35
- assert constants_module.MINIMUM_SUBSTANTIVE_PROSE_CHARS > 0
36
-
37
-
38
- def test_fenced_code_block_pattern_matches_triple_backtick_block() -> None:
39
- sample_markdown = "before ```python\ncode\n``` after"
40
- match = constants_module.FENCED_CODE_BLOCK_PATTERN.search(sample_markdown)
41
- assert match is not None
42
- assert match.group(0).startswith("```")
43
- assert match.group(0).endswith("```")
44
-
45
-
46
- def test_inline_code_pattern_matches_single_backtick_span() -> None:
47
- match = constants_module.INLINE_CODE_PATTERN.search("see `value` here")
48
- assert match is not None
49
- assert match.group(0) == "`value`"
50
-
51
-
52
- def test_heading_line_pattern_matches_atx_heading() -> None:
53
- match = constants_module.HEADING_LINE_PATTERN.search("## Description\n")
54
- assert match is not None
55
- assert match.group(0).strip() == "## Description"
56
-
57
-
58
- def test_bold_pair_pattern_captures_inner_text() -> None:
59
- match = constants_module.BOLD_PAIR_PATTERN.search("this is **bold** text")
60
- assert match is not None
61
- assert match.group(1) == "bold"
62
-
63
-
64
- def test_bullet_marker_pattern_strips_dash_bullet_from_line() -> None:
65
- stripped_line = constants_module.BULLET_MARKER_PATTERN.sub("", "- first item")
66
- assert stripped_line == "first item"
67
-
68
-
69
- def test_blockquote_marker_pattern_strips_quote_marker_from_line() -> None:
70
- stripped_line = constants_module.BLOCKQUOTE_MARKER_PATTERN.sub("", "> quoted line")
71
- assert stripped_line == "quoted line"
72
-
73
-
74
- def test_link_text_pattern_captures_anchor_text() -> None:
75
- match = constants_module.LINK_TEXT_PATTERN.search("See [the docs](https://example.com) now")
76
- assert match is not None
77
- assert match.group(1) == "the docs"
78
-
79
-
80
- def test_whitespace_run_pattern_collapses_multiple_spaces() -> None:
81
- collapsed_text = constants_module.WHITESPACE_RUN_PATTERN.sub(" ", "a b\t\tc\n\nd")
82
- assert collapsed_text == "a b c d"
@@ -1,20 +0,0 @@
1
- """Configuration constants for claude_permissions_common shared helpers."""
2
-
3
- from __future__ import annotations
4
-
5
- TEXT_FILE_ENCODING: str = "utf-8"
6
- ALL_PERMISSION_ALLOW_TOOLS: tuple[str, ...] = ("Edit", "Write", "Read")
7
- AUTO_MODE_ENVIRONMENT_ENTRY_TEMPLATE: str = (
8
- "Trusted local workspace: {project_path}/.claude/** is the user's "
9
- "project Claude Code config tree; edits inside are routine"
10
- )
11
- ATOMIC_WRITE_TEMPORARY_SUFFIX: str = ".tmp"
12
- GIT_DIRECTORY_MARKER: str = ".git"
13
- CLAUDE_DIRECTORY_MARKER: str = ".claude"
14
- CLAUDE_USER_SETTINGS_FILENAME: str = "settings.json"
15
- DEFAULT_SETTINGS_FILE_MODE: int = 0o600
16
- SETTINGS_PERMISSIONS_KEY: str = "permissions"
17
- SETTINGS_ALLOW_KEY: str = "allow"
18
- SETTINGS_ADDITIONAL_DIRECTORIES_KEY: str = "additionalDirectories"
19
- SETTINGS_AUTO_MODE_KEY: str = "autoMode"
20
- SETTINGS_ENVIRONMENT_KEY: str = "environment"