@tekmidian/pai 0.15.0 → 0.17.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/README.md +11 -3
- package/dist/{auto-route-CLYcToZ5.mjs → auto-route-CdvjEWs5.mjs} +4 -4
- package/dist/{auto-route-CLYcToZ5.mjs.map → auto-route-CdvjEWs5.mjs.map} +1 -1
- package/dist/auto-route-DkIRampF.mjs +86 -0
- package/dist/auto-route-DkIRampF.mjs.map +1 -0
- package/dist/{kg-extraction-C8DEUHTS.mjs → checkpoint-block-Bl0HD3U2.mjs} +442 -13
- package/dist/checkpoint-block-Bl0HD3U2.mjs.map +1 -0
- package/dist/checkpoint-block-C05sK_HK.mjs +1056 -0
- package/dist/checkpoint-block-C05sK_HK.mjs.map +1 -0
- package/dist/checkpoint-block-CUCG10Uh.mjs +1022 -0
- package/dist/checkpoint-block-CUCG10Uh.mjs.map +1 -0
- package/dist/checkpoint-block-D5IyLLTr.mjs +1062 -0
- package/dist/checkpoint-block-D5IyLLTr.mjs.map +1 -0
- package/dist/cli/index.mjs +14 -12
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/program.d.mts.map +1 -1
- package/dist/cli/program.mjs +31 -16
- package/dist/cli/program.mjs.map +1 -1
- package/dist/{clusters-BYPw7vfW.mjs → clusters-CKWDwcMX.mjs} +2 -2
- package/dist/{clusters-BYPw7vfW.mjs.map → clusters-CKWDwcMX.mjs.map} +1 -1
- package/dist/{config-C8m-tPhP.mjs → config-DGOfoGfm.mjs} +1 -1
- package/dist/{config-C8m-tPhP.mjs.map → config-DGOfoGfm.mjs.map} +1 -1
- package/dist/daemon/index.mjs +18 -16
- package/dist/daemon/index.mjs.map +1 -1
- package/dist/{daemon-COSvHTNu.mjs → daemon-1rKbN-fd.mjs} +31 -30
- package/dist/{daemon-COSvHTNu.mjs.map → daemon-1rKbN-fd.mjs.map} +1 -1
- package/dist/daemon-9AL6Pwh-.mjs +1581 -0
- package/dist/daemon-9AL6Pwh-.mjs.map +1 -0
- package/dist/daemon-Bbk-_rV6.mjs +1581 -0
- package/dist/daemon-Bbk-_rV6.mjs.map +1 -0
- package/dist/daemon-BvcaMk4g.mjs +1581 -0
- package/dist/daemon-BvcaMk4g.mjs.map +1 -0
- package/dist/daemon-CTDEfwcq.mjs +1576 -0
- package/dist/daemon-CTDEfwcq.mjs.map +1 -0
- package/dist/daemon-ClbfaF60.mjs +1576 -0
- package/dist/daemon-ClbfaF60.mjs.map +1 -0
- package/dist/daemon-DDQNDgYF.mjs +1576 -0
- package/dist/daemon-DDQNDgYF.mjs.map +1 -0
- package/dist/daemon-DU8yDWlQ.mjs +1576 -0
- package/dist/daemon-DU8yDWlQ.mjs.map +1 -0
- package/dist/daemon-NJr4iSW3.mjs +1576 -0
- package/dist/daemon-NJr4iSW3.mjs.map +1 -0
- package/dist/daemon-mcp/index.mjs +2 -2
- package/dist/db-BtuN768f.mjs +206 -0
- package/dist/db-BtuN768f.mjs.map +1 -0
- package/dist/{db-CmYbAVCD.mjs → db-CYmBWcjh.mjs} +1 -1
- package/dist/{db-CmYbAVCD.mjs.map → db-CYmBWcjh.mjs.map} +1 -1
- package/dist/{detect-KjycLtXM.mjs → detect-Bf2z-oKB.mjs} +1 -1
- package/dist/{detect-KjycLtXM.mjs.map → detect-Bf2z-oKB.mjs.map} +1 -1
- package/dist/{detector-ZiOhHszd.mjs → detector-Bwzm3E_x.mjs} +2 -2
- package/dist/{detector-ZiOhHszd.mjs.map → detector-Bwzm3E_x.mjs.map} +1 -1
- package/dist/detector-rq9JGTmS.mjs +74 -0
- package/dist/detector-rq9JGTmS.mjs.map +1 -0
- package/dist/embeddings-Bn86ssxR.mjs +119 -0
- package/dist/embeddings-Bn86ssxR.mjs.map +1 -0
- package/dist/{embeddings-BJPOcbik.mjs → embeddings-DGRAPAYb.mjs} +1 -1
- package/dist/{embeddings-BJPOcbik.mjs.map → embeddings-DGRAPAYb.mjs.map} +1 -1
- package/dist/{factory-Q88X1bAN.mjs → factory-0-57Ac-7.mjs} +8 -5
- package/dist/{factory-Q88X1bAN.mjs.map → factory-0-57Ac-7.mjs.map} +1 -1
- package/dist/factory-HcQsQqh0.mjs +72 -0
- package/dist/factory-HcQsQqh0.mjs.map +1 -0
- package/dist/factory-PDXQdkLO.mjs +72 -0
- package/dist/factory-PDXQdkLO.mjs.map +1 -0
- package/dist/{helpers-IjZkXBhj.mjs → helpers-crDEr6S2.mjs} +1 -1
- package/dist/{helpers-IjZkXBhj.mjs.map → helpers-crDEr6S2.mjs.map} +1 -1
- package/dist/hooks/capture-all-events.mjs.map +1 -1
- package/dist/hooks/capture-session-summary.mjs.map +1 -1
- package/dist/hooks/capture-tool-output.mjs.map +1 -1
- package/dist/hooks/cleanup-session-files.mjs.map +1 -1
- package/dist/hooks/context-compression-hook.mjs +336 -48
- package/dist/hooks/context-compression-hook.mjs.map +4 -4
- package/dist/hooks/initialize-session.mjs.map +1 -1
- package/dist/hooks/inject-observations.mjs.map +1 -1
- package/dist/hooks/load-core-context.mjs.map +1 -1
- package/dist/hooks/load-project-context.mjs +188 -6
- package/dist/hooks/load-project-context.mjs.map +4 -4
- package/dist/hooks/observe.mjs.map +1 -1
- package/dist/hooks/post-compact-inject.mjs.map +1 -1
- package/dist/hooks/security-validator.mjs.map +1 -1
- package/dist/hooks/session-commands.mjs.map +1 -1
- package/dist/hooks/stop-hook.mjs +331 -43
- package/dist/hooks/stop-hook.mjs.map +4 -4
- package/dist/hooks/subagent-stop-hook.mjs.map +1 -1
- package/dist/hooks/sync-todo-to-md.mjs.map +1 -1
- package/dist/hooks/update-tab-on-action.mjs.map +1 -1
- package/dist/hooks/update-tab-titles.mjs.map +1 -1
- package/dist/hooks/whisper-rules.mjs.map +1 -1
- package/dist/index.d.mts +0 -9
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +10 -9
- package/dist/indexer-AEcT8wHf.mjs +1 -0
- package/dist/{indexer-backend-Cm5RS7y0.mjs → indexer-backend-BQZXoarl.mjs} +3 -3
- package/dist/{indexer-backend-Cm5RS7y0.mjs.map → indexer-backend-BQZXoarl.mjs.map} +1 -1
- package/dist/indexer-backend-BmaS2VcD.mjs +299 -0
- package/dist/indexer-backend-BmaS2VcD.mjs.map +1 -0
- package/dist/indexer-backend-COrJ5rjS.mjs +299 -0
- package/dist/indexer-backend-COrJ5rjS.mjs.map +1 -0
- package/dist/indexer-backend-NwD7TBGh.mjs +301 -0
- package/dist/indexer-backend-NwD7TBGh.mjs.map +1 -0
- package/dist/{ipc-client-CoyUHPod.mjs → ipc-client-vO2GV325.mjs} +1 -1
- package/dist/{ipc-client-CoyUHPod.mjs.map → ipc-client-vO2GV325.mjs.map} +1 -1
- package/dist/{kg-entity-LXblD7LZ.mjs → kg-entity-DbOMPdF9.mjs} +1 -1
- package/dist/{kg-entity-LXblD7LZ.mjs.map → kg-entity-DbOMPdF9.mjs.map} +1 -1
- package/dist/{latent-ideas-RyC6eyqI.mjs → latent-ideas-BwM3WdBp.mjs} +4 -4
- package/dist/{latent-ideas-RyC6eyqI.mjs.map → latent-ideas-BwM3WdBp.mjs.map} +1 -1
- package/dist/latent-ideas-C6IFFT0-.mjs +191 -0
- package/dist/latent-ideas-C6IFFT0-.mjs.map +1 -0
- package/dist/{main-resolver-uFxNiDy7.mjs → main-resolver-DimnS2qP.mjs} +22 -22
- package/dist/{main-resolver-uFxNiDy7.mjs.map → main-resolver-DimnS2qP.mjs.map} +1 -1
- package/dist/{migrate-B02wDDgS.mjs → migrate-fLD6rAdO.mjs} +2 -2
- package/dist/{migrate-B02wDDgS.mjs.map → migrate-fLD6rAdO.mjs.map} +1 -1
- package/dist/{neighborhood-CklGIB8r.mjs → neighborhood-CzRNG2Oy.mjs} +2 -2
- package/dist/{neighborhood-CklGIB8r.mjs.map → neighborhood-CzRNG2Oy.mjs.map} +1 -1
- package/dist/neighborhood-DZY0wkpE.mjs +135 -0
- package/dist/neighborhood-DZY0wkpE.mjs.map +1 -0
- package/dist/{note-context-qvZlVXVO.mjs → note-context-CrfMbr4R.mjs} +1 -1
- package/dist/{note-context-qvZlVXVO.mjs.map → note-context-CrfMbr4R.mjs.map} +1 -1
- package/dist/{pai-marker-HVBwwBW-.mjs → pai-marker-B20KqhA8.mjs} +44 -2
- package/dist/pai-marker-B20KqhA8.mjs.map +1 -0
- package/dist/pick-B0q5cy79.mjs +11785 -0
- package/dist/pick-B0q5cy79.mjs.map +1 -0
- package/dist/{pick-r95zvydr.mjs → pick-B3zEYjin.mjs} +2288 -497
- package/dist/pick-B3zEYjin.mjs.map +1 -0
- package/dist/pick-BHE9_iA0.mjs +11947 -0
- package/dist/pick-BHE9_iA0.mjs.map +1 -0
- package/dist/pick-BaTjoNqM.mjs +11544 -0
- package/dist/pick-BaTjoNqM.mjs.map +1 -0
- package/dist/pick-C6JbZfN4.mjs +11893 -0
- package/dist/pick-C6JbZfN4.mjs.map +1 -0
- package/dist/pick-CK-bJyjp.mjs +11883 -0
- package/dist/pick-CK-bJyjp.mjs.map +1 -0
- package/dist/pick-CLBkGHyI.mjs +11826 -0
- package/dist/pick-CLBkGHyI.mjs.map +1 -0
- package/dist/pick-CTOif21h.mjs +11544 -0
- package/dist/pick-CTOif21h.mjs.map +1 -0
- package/dist/pick-Ck2cdQgD.mjs +11893 -0
- package/dist/pick-Ck2cdQgD.mjs.map +1 -0
- package/dist/pick-Cr4nJzHA.mjs +11914 -0
- package/dist/pick-Cr4nJzHA.mjs.map +1 -0
- package/dist/pick-D4dFfKAu.mjs +11893 -0
- package/dist/pick-D4dFfKAu.mjs.map +1 -0
- package/dist/pick-D7-lYiGX.mjs +11543 -0
- package/dist/pick-D7-lYiGX.mjs.map +1 -0
- package/dist/pick-DMjCY0np.mjs +11875 -0
- package/dist/pick-DMjCY0np.mjs.map +1 -0
- package/dist/pick-DbX4OrCP.mjs +11543 -0
- package/dist/pick-DbX4OrCP.mjs.map +1 -0
- package/dist/pick-iCiPjlnC.mjs +11893 -0
- package/dist/pick-iCiPjlnC.mjs.map +1 -0
- package/dist/pick-sF9GnQUS.mjs +11893 -0
- package/dist/pick-sF9GnQUS.mjs.map +1 -0
- package/dist/pick-yHKUrnfG.mjs +11793 -0
- package/dist/pick-yHKUrnfG.mjs.map +1 -0
- package/dist/postgres-BJxiqtqg.mjs +891 -0
- package/dist/postgres-BJxiqtqg.mjs.map +1 -0
- package/dist/{postgres-B451Hnkm.mjs → postgres-zjKqPl4X.mjs} +2 -2
- package/dist/{postgres-B451Hnkm.mjs.map → postgres-zjKqPl4X.mjs.map} +1 -1
- package/dist/{query-feedback-fapbqRgh.mjs → query-feedback-BoY8_Dbb.mjs} +1 -1
- package/dist/{query-feedback-fapbqRgh.mjs.map → query-feedback-BoY8_Dbb.mjs.map} +1 -1
- package/dist/{reranker-C08R99zn.mjs → reranker-CMNZcfVx.mjs} +1 -1
- package/dist/{reranker-C08R99zn.mjs.map → reranker-CMNZcfVx.mjs.map} +1 -1
- package/dist/{search-HcdKtMla.mjs → search-CpTv1I24.mjs} +3 -3
- package/dist/{search-HcdKtMla.mjs.map → search-CpTv1I24.mjs.map} +1 -1
- package/dist/search-i2nlQ-JM.mjs +298 -0
- package/dist/search-i2nlQ-JM.mjs.map +1 -0
- package/dist/skills/End/SKILL.md +78 -0
- package/dist/skills/Pause/SKILL.md +84 -0
- package/dist/sqlite-CZ0LFili.mjs +271 -0
- package/dist/sqlite-CZ0LFili.mjs.map +1 -0
- package/dist/{sqlite-DQpY1Esi.mjs → sqlite-ChSCYwiC.mjs} +3 -3
- package/dist/{sqlite-DQpY1Esi.mjs.map → sqlite-ChSCYwiC.mjs.map} +1 -1
- package/dist/sqlite-Nw8ZW5Rj.mjs +271 -0
- package/dist/sqlite-Nw8ZW5Rj.mjs.map +1 -0
- package/dist/{state-BIlxNRUn.mjs → state-BXIdbxDs.mjs} +1 -1
- package/dist/{state-BIlxNRUn.mjs.map → state-BXIdbxDs.mjs.map} +1 -1
- package/dist/{stop-words-BwplsQ3z.mjs → stop-words-BaMEGVeY.mjs} +1 -1
- package/dist/{stop-words-BwplsQ3z.mjs.map → stop-words-BaMEGVeY.mjs.map} +1 -1
- package/dist/{sync-uPR4g438.mjs → sync--BoxBBok.mjs} +6 -205
- package/dist/sync--BoxBBok.mjs.map +1 -0
- package/dist/sync-CmBKOL3K.mjs +310 -0
- package/dist/sync-CmBKOL3K.mjs.map +1 -0
- package/dist/{themes-Cg8oG9Ra.mjs → themes-BBOlGXAg.mjs} +3 -3
- package/dist/{themes-Cg8oG9Ra.mjs.map → themes-BBOlGXAg.mjs.map} +1 -1
- package/dist/themes-nvRM8v84.mjs +148 -0
- package/dist/themes-nvRM8v84.mjs.map +1 -0
- package/dist/tools-B6BmaAvz.mjs +1939 -0
- package/dist/tools-B6BmaAvz.mjs.map +1 -0
- package/dist/tools-CAIY8TAb.mjs +1939 -0
- package/dist/tools-CAIY8TAb.mjs.map +1 -0
- package/dist/{tools-Op3C2Nm6.mjs → tools-CznjzfYz.mjs} +26 -26
- package/dist/{tools-Op3C2Nm6.mjs.map → tools-CznjzfYz.mjs.map} +1 -1
- package/dist/{trace-CLK-NPkb.mjs → trace-B8oz1ok0.mjs} +1 -1
- package/dist/{trace-CLK-NPkb.mjs.map → trace-B8oz1ok0.mjs.map} +1 -1
- package/dist/{utils-CqqgB0dH.mjs → utils-BAxjW3j8.mjs} +1 -1
- package/dist/{utils-CqqgB0dH.mjs.map → utils-BAxjW3j8.mjs.map} +1 -1
- package/dist/{vault-indexer-Dn_BccVK.mjs → vault-indexer-DrttVeEk.mjs} +2 -2
- package/dist/{vault-indexer-Dn_BccVK.mjs.map → vault-indexer-DrttVeEk.mjs.map} +1 -1
- package/dist/{work-queue-worker-BwouhVkb.mjs → work-queue-worker-BPXLkEik.mjs} +47 -30
- package/dist/work-queue-worker-BPXLkEik.mjs.map +1 -0
- package/dist/work-queue-worker-BoHIH70K.mjs +1856 -0
- package/dist/work-queue-worker-BoHIH70K.mjs.map +1 -0
- package/dist/work-queue-worker-CZGcQoLw.mjs +1856 -0
- package/dist/work-queue-worker-CZGcQoLw.mjs.map +1 -0
- package/dist/work-queue-worker-D05Ze9e9.mjs +1856 -0
- package/dist/work-queue-worker-D05Ze9e9.mjs.map +1 -0
- package/dist/work-queue-worker-DdXFINfH.mjs +1856 -0
- package/dist/work-queue-worker-DdXFINfH.mjs.map +1 -0
- package/dist/{zettelkasten-NwBD1NsT.mjs → zettelkasten-CTlt1dGv.mjs} +4 -4
- package/dist/{zettelkasten-NwBD1NsT.mjs.map → zettelkasten-CTlt1dGv.mjs.map} +1 -1
- package/dist/zettelkasten-D0N_A_OE.mjs +1063 -0
- package/dist/zettelkasten-D0N_A_OE.mjs.map +1 -0
- package/docs/commands/README.md +18 -0
- package/docs/commands/backup.md +1 -1
- package/docs/commands/clear-names.md +1 -1
- package/docs/commands/daemon.md +1 -1
- package/docs/commands/db.md +1 -1
- package/docs/commands/end.md +4 -1
- package/docs/commands/help.md +1 -1
- package/docs/commands/kg.md +1 -1
- package/docs/commands/mcp.md +1 -1
- package/docs/commands/memory.md +1 -1
- package/docs/commands/notify.md +1 -1
- package/docs/commands/observation.md +1 -1
- package/docs/commands/obsidian.md +1 -1
- package/docs/commands/pause.md +5 -2
- package/docs/commands/project.md +1 -1
- package/docs/commands/projects.md +1 -1
- package/docs/commands/registry.md +18 -1
- package/docs/commands/restore.md +1 -1
- package/docs/commands/session.md +295 -0
- package/docs/commands/sessions.md +1 -1
- package/docs/commands/setup.md +1 -1
- package/docs/commands/shell-init.md +1 -1
- package/docs/commands/skill.md +1 -1
- package/docs/commands/task.md +1 -1
- package/docs/commands/topic.md +1 -1
- package/docs/commands/update.md +1 -1
- package/docs/commands/zettel.md +1 -1
- package/package.json +1 -1
- package/plugins/context-preservation/hooks/hooks.json +12 -1
- package/scripts/build-hooks.mjs +33 -3
- package/src/hooks/session-autosave.sh +69 -0
- package/src/hooks/session-stop.sh +32 -1
- package/src/hooks/ts/lib/project-utils/todo.test.ts +132 -0
- package/src/hooks/ts/lib/project-utils/todo.ts +60 -34
- package/src/hooks/ts/session-start/load-project-context.ts +44 -0
- package/dist/kg-extraction-C8DEUHTS.mjs.map +0 -1
- package/dist/pai-marker-HVBwwBW-.mjs.map +0 -1
- package/dist/pick-r95zvydr.mjs.map +0 -1
- package/dist/sync-uPR4g438.mjs.map +0 -1
- package/dist/work-queue-worker-BwouhVkb.mjs.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["
|
|
3
|
+
"sources": ["../../../src/hooks/ts/pre-tool-use/security-validator.ts"],
|
|
4
4
|
"sourcesContent": ["#!/usr/bin/env node\n\n/**\n * security-validator.ts - PreToolUse Security Validation Hook\n *\n * Fast pattern-based security validation for Bash commands.\n * Blocks commands matching known attack patterns before execution.\n *\n * Design Principles:\n * - Fast path: Most commands allowed with minimal processing\n * - Pre-compiled regex patterns at module load\n * - Only log/block on high-confidence attack detection\n * - Fail open on errors (don't break legitimate work)\n *\n * CUSTOMIZATION REQUIRED:\n * This template includes basic examples. Add your own security patterns\n * based on your threat model and environment.\n */\n\nimport { appendFileSync, mkdirSync, existsSync } from 'fs';\nimport { dirname } from 'path';\n\n// ============================================================================\n// ATTACK PATTERNS - CUSTOMIZE THESE FOR YOUR ENVIRONMENT\n// ============================================================================\n\n// Example: Reverse Shell Patterns (BLOCK - rarely legitimate)\nconst REVERSE_SHELL_PATTERNS: RegExp[] = [\n /\\/dev\\/(tcp|udp)\\/[0-9]/, // Bash TCP/UDP device\n /bash\\s+-i\\s+>&?\\s*\\/dev\\//, // Interactive bash redirect\n // Add your own reverse shell patterns here\n];\n\n// Example: Instruction Override (BLOCK - prompt injection)\nconst INSTRUCTION_OVERRIDE_PATTERNS: RegExp[] = [\n /ignore\\s+(all\\s+)?previous\\s+instructions?/i,\n /disregard\\s+(all\\s+)?(prior|previous)\\s+(instructions?|rules?)/i,\n // Add your own prompt injection patterns here\n];\n\n// Example: Catastrophic Deletion Patterns (BLOCK - filesystem destruction)\nconst CATASTROPHIC_DELETION_PATTERNS: RegExp[] = [\n // Trailing tilde bypass\n /\\s+~\\/?(\\s*$|\\s+)/, // Space then ~/ at end\n /\\brm\\s+(-[rfivd]+\\s+)*\\S+\\s+~\\/?/, // rm something ~/\n\n // Relative path recursive deletion\n /\\brm\\s+(-[rfivd]+\\s+)*\\.\\/\\s*$/, // rm -rf ./\n /\\brm\\s+(-[rfivd]+\\s+)*\\.\\.\\/\\s*$/, // rm -rf ../\n\n // Add your own dangerous deletion patterns here\n];\n\n// Example: Dangerous File Operations (BLOCK - data destruction)\nconst DANGEROUS_FILE_OPS_PATTERNS: RegExp[] = [\n /\\bchmod\\s+(-R\\s+)?0{3,}/, // chmod 000\n // Add your own dangerous file operation patterns here\n];\n\n// OPTIONAL: Operations that require confirmation instead of blocking\nconst DANGEROUS_GIT_PATTERNS: RegExp[] = [\n /\\bgit\\s+push\\s+.*(-f\\b|--force)/i, // git push --force\n /\\bgit\\s+reset\\s+--hard/i, // git reset --hard\n // Add your own git safety patterns here\n];\n\n// Combined patterns for fast iteration\nconst ALL_BLOCK_PATTERNS: { category: string; patterns: RegExp[] }[] = [\n { category: 'reverse_shell', patterns: REVERSE_SHELL_PATTERNS },\n { category: 'instruction_override', patterns: INSTRUCTION_OVERRIDE_PATTERNS },\n { category: 'catastrophic_deletion', patterns: CATASTROPHIC_DELETION_PATTERNS },\n { category: 'dangerous_file_ops', patterns: DANGEROUS_FILE_OPS_PATTERNS },\n];\n\nconst CONFIRM_PATTERNS: { category: string; patterns: RegExp[] }[] = [\n { category: 'dangerous_git', patterns: DANGEROUS_GIT_PATTERNS },\n];\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\ninterface HookInput {\n session_id: string;\n tool_name: string;\n tool_input: Record<string, unknown> | string;\n}\n\ninterface HookOutput {\n permissionDecision: 'allow' | 'deny';\n additionalContext?: string;\n feedback?: string;\n}\n\n// ============================================================================\n// DETECTION LOGIC\n// ============================================================================\n\ninterface DetectionResult {\n blocked: boolean;\n requiresConfirmation?: boolean;\n category?: string;\n pattern?: string;\n}\n\nfunction detectAttack(content: string): DetectionResult {\n // First check for hard blocks\n for (const { category, patterns } of ALL_BLOCK_PATTERNS) {\n for (const pattern of patterns) {\n if (pattern.test(content)) {\n return { blocked: true, category, pattern: pattern.source };\n }\n }\n }\n\n // Then check for confirmation-required patterns\n for (const { category, patterns } of CONFIRM_PATTERNS) {\n for (const pattern of patterns) {\n if (pattern.test(content)) {\n return { blocked: false, requiresConfirmation: true, category, pattern: pattern.source };\n }\n }\n }\n\n return { blocked: false };\n}\n\n// ============================================================================\n// ASYNC LOGGING (fire-and-forget on block only)\n// ============================================================================\n\nfunction logSecurityEvent(event: Record<string, unknown>): void {\n const logPath = `${process.env.PAI_DIR || '~/.claude'}/history/security/security-events.jsonl`;\n const entry = JSON.stringify({ timestamp: new Date().toISOString(), ...event }) + '\\n';\n\n try {\n const dir = dirname(logPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n appendFileSync(logPath, entry);\n } catch {\n // Silently fail - logging should never break the hook\n }\n}\n\n// ============================================================================\n// MAIN HOOK LOGIC\n// ============================================================================\n\nasync function main(): Promise<void> {\n let input: HookInput;\n\n try {\n // Read stdin\n const chunks: Buffer[] = [];\n const timeoutPromise = new Promise<Buffer[]>((_, reject) =>\n setTimeout(() => reject(new Error('timeout')), 100)\n );\n const readPromise = (async () => {\n for await (const chunk of process.stdin) {\n chunks.push(chunk);\n }\n return chunks;\n })();\n\n let text = '';\n try {\n const result = await Promise.race([readPromise, timeoutPromise]);\n text = Buffer.concat(result).toString('utf-8');\n } catch {\n console.log(JSON.stringify({ permissionDecision: 'allow' }));\n return;\n }\n\n if (!text.trim()) {\n console.log(JSON.stringify({ permissionDecision: 'allow' }));\n return;\n }\n\n input = JSON.parse(text);\n } catch {\n // Parse error or timeout - fail open\n console.log(JSON.stringify({ permissionDecision: 'allow' }));\n return;\n }\n\n // Only validate Bash commands\n if (input.tool_name !== 'Bash') {\n console.log(JSON.stringify({ permissionDecision: 'allow' }));\n return;\n }\n\n // Extract command string\n const command = typeof input.tool_input === 'string'\n ? input.tool_input\n : (input.tool_input?.command as string) || '';\n\n if (!command) {\n console.log(JSON.stringify({ permissionDecision: 'allow' }));\n return;\n }\n\n // Check all patterns\n const result = detectAttack(command);\n\n if (result.blocked) {\n // Log and block\n logSecurityEvent({\n type: 'attack_blocked',\n category: result.category,\n pattern: result.pattern,\n command: command.slice(0, 200), // Truncate for log\n session_id: input.session_id,\n });\n\n const output: HookOutput = {\n permissionDecision: 'deny',\n additionalContext: `SECURITY: Blocked ${result.category} pattern`,\n feedback: `This command matched a security pattern (${result.category}). If this is legitimate, please rephrase the command.`,\n };\n\n console.log(JSON.stringify(output));\n process.exit(2); // Exit 2 = blocking error\n }\n\n if (result.requiresConfirmation) {\n // Log warning and require confirmation\n logSecurityEvent({\n type: 'confirmation_required',\n category: result.category,\n pattern: result.pattern,\n command: command.slice(0, 200),\n session_id: input.session_id,\n });\n\n const output: HookOutput = {\n permissionDecision: 'deny',\n additionalContext: `DANGEROUS: ${result.category} operation requires confirmation`,\n feedback: `This is a dangerous operation (${command.slice(0, 50)}...). This can cause data loss. If you're sure, explicitly confirm this command.`,\n };\n\n console.log(JSON.stringify(output));\n process.exit(2); // Exit 2 = requires user confirmation\n }\n\n // Allow - no logging, immediate exit\n console.log(JSON.stringify({ permissionDecision: 'allow' }));\n}\n\n// ============================================================================\n// RUN\n// ============================================================================\n\nmain().catch(() => {\n // On any error, fail open\n console.log(JSON.stringify({ permissionDecision: 'allow' }));\n});\n"],
|
|
5
5
|
"mappings": ";;;AAmBA,SAAS,gBAAgB,WAAW,kBAAkB;AACtD,SAAS,eAAe;AAOxB,IAAM,yBAAmC;AAAA,EACvC;AAAA;AAAA,EACA;AAAA;AAAA;AAEF;AAGA,IAAM,gCAA0C;AAAA,EAC9C;AAAA,EACA;AAAA;AAEF;AAGA,IAAM,iCAA2C;AAAA;AAAA,EAE/C;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA;AAGF;AAGA,IAAM,8BAAwC;AAAA,EAC5C;AAAA;AAAA;AAEF;AAGA,IAAM,yBAAmC;AAAA,EACvC;AAAA;AAAA,EACA;AAAA;AAAA;AAEF;AAGA,IAAM,qBAAiE;AAAA,EACrE,EAAE,UAAU,iBAAiB,UAAU,uBAAuB;AAAA,EAC9D,EAAE,UAAU,wBAAwB,UAAU,8BAA8B;AAAA,EAC5E,EAAE,UAAU,yBAAyB,UAAU,+BAA+B;AAAA,EAC9E,EAAE,UAAU,sBAAsB,UAAU,4BAA4B;AAC1E;AAEA,IAAM,mBAA+D;AAAA,EACnE,EAAE,UAAU,iBAAiB,UAAU,uBAAuB;AAChE;AA6BA,SAAS,aAAa,SAAkC;AAEtD,aAAW,EAAE,UAAU,SAAS,KAAK,oBAAoB;AACvD,eAAW,WAAW,UAAU;AAC9B,UAAI,QAAQ,KAAK,OAAO,GAAG;AACzB,eAAO,EAAE,SAAS,MAAM,UAAU,SAAS,QAAQ,OAAO;AAAA,MAC5D;AAAA,IACF;AAAA,EACF;AAGA,aAAW,EAAE,UAAU,SAAS,KAAK,kBAAkB;AACrD,eAAW,WAAW,UAAU;AAC9B,UAAI,QAAQ,KAAK,OAAO,GAAG;AACzB,eAAO,EAAE,SAAS,OAAO,sBAAsB,MAAM,UAAU,SAAS,QAAQ,OAAO;AAAA,MACzF;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,SAAS,MAAM;AAC1B;AAMA,SAAS,iBAAiB,OAAsC;AAC9D,QAAM,UAAU,GAAG,QAAQ,IAAI,WAAW,WAAW;AACrD,QAAM,QAAQ,KAAK,UAAU,EAAE,YAAW,oBAAI,KAAK,GAAE,YAAY,GAAG,GAAG,MAAM,CAAC,IAAI;AAElF,MAAI;AACF,UAAM,MAAM,QAAQ,OAAO;AAC3B,QAAI,CAAC,WAAW,GAAG,GAAG;AACpB,gBAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,IACpC;AACA,mBAAe,SAAS,KAAK;AAAA,EAC/B,QAAQ;AAAA,EAER;AACF;AAMA,eAAe,OAAsB;AACnC,MAAI;AAEJ,MAAI;AAEF,UAAM,SAAmB,CAAC;AAC1B,UAAM,iBAAiB,IAAI;AAAA,MAAkB,CAAC,GAAG,WAC/C,WAAW,MAAM,OAAO,IAAI,MAAM,SAAS,CAAC,GAAG,GAAG;AAAA,IACpD;AACA,UAAM,eAAe,YAAY;AAC/B,uBAAiB,SAAS,QAAQ,OAAO;AACvC,eAAO,KAAK,KAAK;AAAA,MACnB;AACA,aAAO;AAAA,IACT,GAAG;AAEH,QAAI,OAAO;AACX,QAAI;AACF,YAAMA,UAAS,MAAM,QAAQ,KAAK,CAAC,aAAa,cAAc,CAAC;AAC/D,aAAO,OAAO,OAAOA,OAAM,EAAE,SAAS,OAAO;AAAA,IAC/C,QAAQ;AACN,cAAQ,IAAI,KAAK,UAAU,EAAE,oBAAoB,QAAQ,CAAC,CAAC;AAC3D;AAAA,IACF;AAEA,QAAI,CAAC,KAAK,KAAK,GAAG;AAChB,cAAQ,IAAI,KAAK,UAAU,EAAE,oBAAoB,QAAQ,CAAC,CAAC;AAC3D;AAAA,IACF;AAEA,YAAQ,KAAK,MAAM,IAAI;AAAA,EACzB,QAAQ;AAEN,YAAQ,IAAI,KAAK,UAAU,EAAE,oBAAoB,QAAQ,CAAC,CAAC;AAC3D;AAAA,EACF;AAGA,MAAI,MAAM,cAAc,QAAQ;AAC9B,YAAQ,IAAI,KAAK,UAAU,EAAE,oBAAoB,QAAQ,CAAC,CAAC;AAC3D;AAAA,EACF;AAGA,QAAM,UAAU,OAAO,MAAM,eAAe,WACxC,MAAM,aACL,MAAM,YAAY,WAAsB;AAE7C,MAAI,CAAC,SAAS;AACZ,YAAQ,IAAI,KAAK,UAAU,EAAE,oBAAoB,QAAQ,CAAC,CAAC;AAC3D;AAAA,EACF;AAGA,QAAM,SAAS,aAAa,OAAO;AAEnC,MAAI,OAAO,SAAS;AAElB,qBAAiB;AAAA,MACf,MAAM;AAAA,MACN,UAAU,OAAO;AAAA,MACjB,SAAS,OAAO;AAAA,MAChB,SAAS,QAAQ,MAAM,GAAG,GAAG;AAAA;AAAA,MAC7B,YAAY,MAAM;AAAA,IACpB,CAAC;AAED,UAAM,SAAqB;AAAA,MACzB,oBAAoB;AAAA,MACpB,mBAAmB,qBAAqB,OAAO,QAAQ;AAAA,MACvD,UAAU,4CAA4C,OAAO,QAAQ;AAAA,IACvE;AAEA,YAAQ,IAAI,KAAK,UAAU,MAAM,CAAC;AAClC,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,OAAO,sBAAsB;AAE/B,qBAAiB;AAAA,MACf,MAAM;AAAA,MACN,UAAU,OAAO;AAAA,MACjB,SAAS,OAAO;AAAA,MAChB,SAAS,QAAQ,MAAM,GAAG,GAAG;AAAA,MAC7B,YAAY,MAAM;AAAA,IACpB,CAAC;AAED,UAAM,SAAqB;AAAA,MACzB,oBAAoB;AAAA,MACpB,mBAAmB,cAAc,OAAO,QAAQ;AAAA,MAChD,UAAU,kCAAkC,QAAQ,MAAM,GAAG,EAAE,CAAC;AAAA,IAClE;AAEA,YAAQ,IAAI,KAAK,UAAU,MAAM,CAAC;AAClC,YAAQ,KAAK,CAAC;AAAA,EAChB;AAGA,UAAQ,IAAI,KAAK,UAAU,EAAE,oBAAoB,QAAQ,CAAC,CAAC;AAC7D;AAMA,KAAK,EAAE,MAAM,MAAM;AAEjB,UAAQ,IAAI,KAAK,UAAU,EAAE,oBAAoB,QAAQ,CAAC,CAAC;AAC7D,CAAC;",
|
|
6
6
|
"names": ["result"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["
|
|
3
|
+
"sources": ["../../../src/hooks/ts/session-start/session-commands.ts"],
|
|
4
4
|
"sourcesContent": ["#!/usr/bin/env node\n\n/**\n * session-commands.ts\n *\n * Tiny SessionStart hook that injects critical session commands inline.\n * Stays under 2KB to avoid persisted-output truncation.\n *\n * This runs BEFORE load-core-context so these commands are always available\n * even when the full CORE skill gets truncated.\n */\n\nconst commands = `<system-reminder>\nSESSION COMMANDS (always available):\n\n- \"go\" / \"continue\" / \"weiter\" \u2192 Read Notes/TODO.md, find ## Continue section, resume work\n- \"pause session\" \u2192 Save state to TODO.md, update session note, stop (no compact)\n- \"end session\" \u2192 Finalize note, commit if needed, start fresh next time\n- \"cpp\" \u2192 Release workflow: version bump \u2192 build \u2192 npm publish \u2192 git add ALL \u2192 ONE commit \u2192 git push\n\nWhen user says \"go\" with no other context, ALWAYS check TODO.md first.\n\nIMPORTANT: Full PAI context may be truncated due to size. For complete instructions (response format, workflows, principles), READ: ~/.claude/Skills/CORE/SKILL.md\n</system-reminder>`;\n\nconsole.log(commands);\n"],
|
|
5
5
|
"mappings": ";;;AAYA,IAAM,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAajB,QAAQ,IAAI,QAAQ;",
|
|
6
6
|
"names": []
|
package/dist/hooks/stop-hook.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/hooks/ts/stop/stop-hook.ts
|
|
4
|
-
import { readFileSync as
|
|
5
|
-
import { join as
|
|
4
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, existsSync as existsSync7, unlinkSync } from "fs";
|
|
5
|
+
import { join as join7, basename as basename3, dirname } from "path";
|
|
6
6
|
import { connect } from "net";
|
|
7
7
|
import { randomUUID } from "crypto";
|
|
8
8
|
|
|
@@ -387,13 +387,292 @@ ${summary || "Session completed."}`
|
|
|
387
387
|
}
|
|
388
388
|
|
|
389
389
|
// src/hooks/ts/lib/project-utils/todo.ts
|
|
390
|
-
import { existsSync as
|
|
391
|
-
import { join as
|
|
390
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
391
|
+
import { join as join6 } from "path";
|
|
392
|
+
|
|
393
|
+
// src/session/checkpoint-block.ts
|
|
394
|
+
import {
|
|
395
|
+
existsSync as existsSync5,
|
|
396
|
+
readFileSync as readFileSync4,
|
|
397
|
+
writeFileSync as writeFileSync2,
|
|
398
|
+
readdirSync as readdirSync3,
|
|
399
|
+
renameSync as renameSync3,
|
|
400
|
+
mkdirSync as mkdirSync3,
|
|
401
|
+
realpathSync
|
|
402
|
+
} from "node:fs";
|
|
403
|
+
import { join as join5 } from "node:path";
|
|
404
|
+
var TODO_LOCATIONS = [
|
|
405
|
+
"Notes/TODO.md",
|
|
406
|
+
".claude/Notes/TODO.md",
|
|
407
|
+
"tasks/todo.md",
|
|
408
|
+
"TODO.md"
|
|
409
|
+
];
|
|
410
|
+
var MARKER_OPEN = "<!-- pai:checkpoint";
|
|
411
|
+
var MARKER_CLOSE = "<!-- /pai:checkpoint -->";
|
|
412
|
+
var CONTINUE_HEADING = "## Continue";
|
|
413
|
+
function findProjectTodo(rootPath) {
|
|
414
|
+
for (const rel of TODO_LOCATIONS) {
|
|
415
|
+
const full = join5(rootPath, rel);
|
|
416
|
+
if (existsSync5(full)) {
|
|
417
|
+
try {
|
|
418
|
+
return { path: full, content: readFileSync4(full, "utf8") };
|
|
419
|
+
} catch {
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
function resolveTodoTarget(rootPath, opts = {}) {
|
|
426
|
+
const found = findProjectTodo(rootPath);
|
|
427
|
+
if (found) return found;
|
|
428
|
+
const notesDir = join5(rootPath, "Notes");
|
|
429
|
+
if (opts.create !== false) {
|
|
430
|
+
try {
|
|
431
|
+
if (!existsSync5(notesDir)) mkdirSync3(notesDir, { recursive: true });
|
|
432
|
+
} catch {
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return { path: join5(notesDir, "TODO.md"), content: "" };
|
|
437
|
+
}
|
|
438
|
+
function parseMarker(line) {
|
|
439
|
+
const trimmed = line.trim();
|
|
440
|
+
if (!trimmed.startsWith(MARKER_OPEN)) return null;
|
|
441
|
+
const attrs = {};
|
|
442
|
+
for (const m of trimmed.matchAll(/([a-zA-Z][\w-]*)="([^"]*)"/g)) {
|
|
443
|
+
attrs[m[1]] = m[2];
|
|
444
|
+
}
|
|
445
|
+
return {
|
|
446
|
+
authored: attrs.authored === "model" ? "model" : "auto",
|
|
447
|
+
session: attrs.session || void 0,
|
|
448
|
+
sessionId: attrs["session-id"] || void 0,
|
|
449
|
+
ts: attrs.ts || void 0
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
var BOILERPLATE_PATTERNS = [
|
|
453
|
+
/^##\s+Continue$/,
|
|
454
|
+
/^<!--\s*\/?pai:checkpoint/,
|
|
455
|
+
/^>\s*\*\*Last session:\*\*/,
|
|
456
|
+
/^>\s*\*\*Paused at:\*\*/,
|
|
457
|
+
/^>\s*Working directory:/,
|
|
458
|
+
/^>\s*Resume with:/,
|
|
459
|
+
/^>\s*_No checkpoint body was recorded/,
|
|
460
|
+
/^-{3,}$/
|
|
461
|
+
];
|
|
462
|
+
var BLANK_LINE = /^>?\s*$/;
|
|
463
|
+
function isBoilerplateOnly(lines) {
|
|
464
|
+
return lines.every((line) => {
|
|
465
|
+
const t = line.trim();
|
|
466
|
+
return BLANK_LINE.test(t) || BOILERPLATE_PATTERNS.some((re) => re.test(t));
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
function extractSectionContent(lines) {
|
|
470
|
+
const kept = [];
|
|
471
|
+
for (const line of lines) {
|
|
472
|
+
const t = line.trim();
|
|
473
|
+
if (BOILERPLATE_PATTERNS.some((re) => re.test(t))) continue;
|
|
474
|
+
kept.push(line);
|
|
475
|
+
}
|
|
476
|
+
return trimBlankEdges(collapseBlankRuns(kept)).join("\n");
|
|
477
|
+
}
|
|
478
|
+
function trimBlankEdges(lines) {
|
|
479
|
+
let start = 0;
|
|
480
|
+
let end = lines.length;
|
|
481
|
+
while (start < end && BLANK_LINE.test(lines[start].trim())) start += 1;
|
|
482
|
+
while (end > start && BLANK_LINE.test(lines[end - 1].trim())) end -= 1;
|
|
483
|
+
return lines.slice(start, end);
|
|
484
|
+
}
|
|
485
|
+
function collapseBlankRuns(lines) {
|
|
486
|
+
const out = [];
|
|
487
|
+
let lastWasBlank = false;
|
|
488
|
+
for (const line of lines) {
|
|
489
|
+
const isBlank = BLANK_LINE.test(line.trim());
|
|
490
|
+
if (isBlank && lastWasBlank) continue;
|
|
491
|
+
out.push(line);
|
|
492
|
+
lastWasBlank = isBlank;
|
|
493
|
+
}
|
|
494
|
+
return out;
|
|
495
|
+
}
|
|
496
|
+
function locateContinue(content) {
|
|
497
|
+
const lines = content.split("\n");
|
|
498
|
+
const startIdx = lines.findIndex((l) => l.trim() === CONTINUE_HEADING);
|
|
499
|
+
if (startIdx === -1) return null;
|
|
500
|
+
let meta = null;
|
|
501
|
+
let markerIdx = -1;
|
|
502
|
+
for (let i = startIdx + 1; i < Math.min(startIdx + 6, lines.length); i++) {
|
|
503
|
+
const parsed = parseMarker(lines[i]);
|
|
504
|
+
if (parsed) {
|
|
505
|
+
meta = parsed;
|
|
506
|
+
markerIdx = i;
|
|
507
|
+
break;
|
|
508
|
+
}
|
|
509
|
+
if (lines[i].trim() !== "") break;
|
|
510
|
+
}
|
|
511
|
+
let endIdx = lines.length;
|
|
512
|
+
if (markerIdx !== -1) {
|
|
513
|
+
const closeIdx = lines.findIndex(
|
|
514
|
+
(l, i) => i > markerIdx && l.trim() === MARKER_CLOSE
|
|
515
|
+
);
|
|
516
|
+
if (closeIdx !== -1) {
|
|
517
|
+
endIdx = closeIdx + 1;
|
|
518
|
+
} else {
|
|
519
|
+
endIdx = heuristicEnd(lines, startIdx);
|
|
520
|
+
}
|
|
521
|
+
} else {
|
|
522
|
+
endIdx = heuristicEnd(lines, startIdx);
|
|
523
|
+
}
|
|
524
|
+
let trailingEnd = endIdx;
|
|
525
|
+
while (trailingEnd < lines.length && lines[trailingEnd].trim() === "") {
|
|
526
|
+
trailingEnd += 1;
|
|
527
|
+
}
|
|
528
|
+
if (trailingEnd < lines.length && lines[trailingEnd].trim() === "---") {
|
|
529
|
+
trailingEnd += 1;
|
|
530
|
+
} else {
|
|
531
|
+
trailingEnd = endIdx;
|
|
532
|
+
}
|
|
533
|
+
return {
|
|
534
|
+
startIdx,
|
|
535
|
+
endIdx: trailingEnd,
|
|
536
|
+
meta,
|
|
537
|
+
lines: lines.slice(startIdx, trailingEnd)
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
function heuristicEnd(lines, startIdx) {
|
|
541
|
+
for (let i = startIdx + 1; i < lines.length; i++) {
|
|
542
|
+
const trimmed = lines[i].trim();
|
|
543
|
+
if (trimmed === "---" || /^##(?!#)/.test(trimmed) && trimmed !== CONTINUE_HEADING) {
|
|
544
|
+
return i;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
return lines.length;
|
|
548
|
+
}
|
|
549
|
+
function stripContinue(content) {
|
|
550
|
+
const found = locateContinue(content);
|
|
551
|
+
if (!found) return content;
|
|
552
|
+
const lines = content.split("\n");
|
|
553
|
+
const before = lines.slice(0, found.startIdx);
|
|
554
|
+
const after = lines.slice(found.endIdx);
|
|
555
|
+
while (after.length > 0 && after[0].trim() === "") after.shift();
|
|
556
|
+
return [...before, ...after].join("\n");
|
|
557
|
+
}
|
|
558
|
+
function escapeAttr(value) {
|
|
559
|
+
return value.replace(/"/g, "'");
|
|
560
|
+
}
|
|
561
|
+
function buildContinueBlock(opts) {
|
|
562
|
+
const ts = opts.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
563
|
+
const attrs = [
|
|
564
|
+
`authored="${opts.authored}"`,
|
|
565
|
+
`session="${escapeAttr(opts.sessionLine)}"`,
|
|
566
|
+
opts.sessionId ? `session-id="${escapeAttr(opts.sessionId)}"` : null,
|
|
567
|
+
`ts="${ts}"`
|
|
568
|
+
].filter(Boolean).join(" ");
|
|
569
|
+
const header = [
|
|
570
|
+
`> **Last session:** ${opts.sessionLine}`,
|
|
571
|
+
`> **Paused at:** ${ts}`,
|
|
572
|
+
">",
|
|
573
|
+
`> Working directory: ${opts.cwd}`
|
|
574
|
+
];
|
|
575
|
+
if (opts.sessionId) {
|
|
576
|
+
header.push(">", `> Resume with: \`claude --resume ${opts.sessionId}\``);
|
|
577
|
+
}
|
|
578
|
+
const body = (opts.body ?? "").trim();
|
|
579
|
+
const parts = [
|
|
580
|
+
CONTINUE_HEADING,
|
|
581
|
+
"",
|
|
582
|
+
`${MARKER_OPEN} ${attrs} -->`,
|
|
583
|
+
"",
|
|
584
|
+
...header
|
|
585
|
+
];
|
|
586
|
+
if (body) {
|
|
587
|
+
parts.push("", body);
|
|
588
|
+
} else {
|
|
589
|
+
parts.push(
|
|
590
|
+
">",
|
|
591
|
+
"> _No checkpoint body was recorded \u2014 see the latest session note._"
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
parts.push("", MARKER_CLOSE, "", "---", "");
|
|
595
|
+
return parts.join("\n");
|
|
596
|
+
}
|
|
597
|
+
function isSameSession(meta, opts) {
|
|
598
|
+
if (meta.sessionId && opts.sessionId) {
|
|
599
|
+
return meta.sessionId === opts.sessionId;
|
|
600
|
+
}
|
|
601
|
+
return meta.session === opts.sessionLine;
|
|
602
|
+
}
|
|
603
|
+
function applyContinue(opts) {
|
|
604
|
+
const target = resolveTodoTarget(opts.rootPath, { create: !opts.dryRun });
|
|
605
|
+
if (!target) {
|
|
606
|
+
return {
|
|
607
|
+
action: "failed",
|
|
608
|
+
path: null,
|
|
609
|
+
block: buildContinueBlock(opts),
|
|
610
|
+
error: "Could not resolve or create a TODO.md target"
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
const existing = locateContinue(target.content);
|
|
614
|
+
let carriedForward = false;
|
|
615
|
+
let effectiveBody = opts.body;
|
|
616
|
+
if (opts.authored === "auto" && existing) {
|
|
617
|
+
if (existing.meta?.authored === "model" && isSameSession(existing.meta, opts)) {
|
|
618
|
+
return {
|
|
619
|
+
action: "preserved",
|
|
620
|
+
path: target.path,
|
|
621
|
+
block: buildContinueBlock(opts),
|
|
622
|
+
preservedMeta: existing.meta
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
if (existing.meta && !(opts.body ?? "").trim() && !isBoilerplateOnly(existing.lines)) {
|
|
626
|
+
return {
|
|
627
|
+
action: "preserved",
|
|
628
|
+
path: target.path,
|
|
629
|
+
block: buildContinueBlock(opts),
|
|
630
|
+
preservedMeta: existing.meta ?? void 0
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
if (!existing.meta && !isBoilerplateOnly(existing.lines)) {
|
|
634
|
+
const salvaged = extractSectionContent(existing.lines);
|
|
635
|
+
if (salvaged) {
|
|
636
|
+
effectiveBody = [
|
|
637
|
+
"_Carried forward from the previous checkpoint (author unknown \u2014 this",
|
|
638
|
+
"block predates checkpoint authorship markers):_",
|
|
639
|
+
"",
|
|
640
|
+
salvaged
|
|
641
|
+
].join("\n");
|
|
642
|
+
carriedForward = true;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
const block = buildContinueBlock({ ...opts, body: effectiveBody });
|
|
647
|
+
if (opts.dryRun) {
|
|
648
|
+
return { action: "written", path: target.path, block, carriedForward };
|
|
649
|
+
}
|
|
650
|
+
const newContent = block + stripContinue(target.content).trimStart();
|
|
651
|
+
const tmpPath = `${target.path}.continue.tmp`;
|
|
652
|
+
try {
|
|
653
|
+
writeFileSync2(tmpPath, newContent, "utf8");
|
|
654
|
+
renameSync3(tmpPath, target.path);
|
|
655
|
+
} catch (err) {
|
|
656
|
+
try {
|
|
657
|
+
if (existsSync5(tmpPath)) renameSync3(tmpPath, `${tmpPath}.dead`);
|
|
658
|
+
} catch {
|
|
659
|
+
}
|
|
660
|
+
return {
|
|
661
|
+
action: "failed",
|
|
662
|
+
path: target.path,
|
|
663
|
+
block,
|
|
664
|
+
error: String(err)
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
return { action: "written", path: target.path, block, carriedForward };
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// src/hooks/ts/lib/project-utils/todo.ts
|
|
392
671
|
function ensureTodoMd(cwd) {
|
|
393
672
|
const todoPath = findTodoPath(cwd);
|
|
394
|
-
if (!
|
|
395
|
-
const parentDir =
|
|
396
|
-
if (!
|
|
673
|
+
if (!existsSync6(todoPath)) {
|
|
674
|
+
const parentDir = join6(todoPath, "..");
|
|
675
|
+
if (!existsSync6(parentDir)) mkdirSync4(parentDir, { recursive: true });
|
|
397
676
|
const content = `# TODO
|
|
398
677
|
|
|
399
678
|
## Current Session
|
|
@@ -408,43 +687,52 @@ function ensureTodoMd(cwd) {
|
|
|
408
687
|
|
|
409
688
|
*Last updated: ${(/* @__PURE__ */ new Date()).toISOString()}*
|
|
410
689
|
`;
|
|
411
|
-
|
|
690
|
+
writeFileSync3(todoPath, content);
|
|
412
691
|
console.error(`Created TODO.md: ${todoPath}`);
|
|
413
692
|
}
|
|
414
693
|
return todoPath;
|
|
415
694
|
}
|
|
416
695
|
function updateTodoContinue(cwd, noteFilename, state, tokenDisplay) {
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
content = titleMatch[1] + continueSection + content.substring(titleMatch[0].length);
|
|
436
|
-
} else {
|
|
437
|
-
content = continueSection + content;
|
|
696
|
+
ensureTodoMd(cwd);
|
|
697
|
+
const result = applyContinue({
|
|
698
|
+
rootPath: cwd,
|
|
699
|
+
authored: "auto",
|
|
700
|
+
// The hooks identify a session by its note filename; `pai pause` resolves
|
|
701
|
+
// the same string from the registry (and falls back to this filename when
|
|
702
|
+
// the registry has no session row). They must agree, because this string
|
|
703
|
+
// is the key that decides whether an authored checkpoint belongs to the
|
|
704
|
+
// current session and must be preserved.
|
|
705
|
+
sessionLine: noteFilename.replace(/\.md$/, ""),
|
|
706
|
+
cwd,
|
|
707
|
+
body: state?.trim() || void 0
|
|
708
|
+
});
|
|
709
|
+
if (result.action === "preserved") {
|
|
710
|
+
console.error(
|
|
711
|
+
"TODO.md ## Continue left intact \u2014 authored checkpoint for this session"
|
|
712
|
+
);
|
|
713
|
+
return;
|
|
438
714
|
}
|
|
439
|
-
|
|
440
|
-
|
|
715
|
+
if (result.action === "failed") {
|
|
716
|
+
console.error(`TODO.md ## Continue update failed: ${result.error}`);
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
try {
|
|
720
|
+
const todoPath = result.path;
|
|
721
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
722
|
+
let content = readFileSync5(todoPath, "utf-8");
|
|
723
|
+
content = content.replace(/(\n---\s*)*(\n\*Last updated:.*\*\s*)+$/g, "");
|
|
724
|
+
content = content.trimEnd() + `
|
|
441
725
|
|
|
442
726
|
---
|
|
443
727
|
|
|
444
728
|
*Last updated: ${now}*
|
|
445
729
|
`;
|
|
446
|
-
|
|
447
|
-
|
|
730
|
+
writeFileSync3(todoPath, content);
|
|
731
|
+
} catch {
|
|
732
|
+
}
|
|
733
|
+
console.error(
|
|
734
|
+
result.carriedForward ? "TODO.md ## Continue section updated (previous content carried forward)" : "TODO.md ## Continue section updated"
|
|
735
|
+
);
|
|
448
736
|
}
|
|
449
737
|
|
|
450
738
|
// src/hooks/ts/stop/stop-hook.ts
|
|
@@ -458,7 +746,7 @@ var AUTO_SAVE_INTERVAL = (() => {
|
|
|
458
746
|
}
|
|
459
747
|
return 15;
|
|
460
748
|
})();
|
|
461
|
-
var SESSION_STATE_DIR =
|
|
749
|
+
var SESSION_STATE_DIR = join7(
|
|
462
750
|
process.env.HOME ?? "/tmp",
|
|
463
751
|
".config",
|
|
464
752
|
"pai",
|
|
@@ -466,9 +754,9 @@ var SESSION_STATE_DIR = join6(
|
|
|
466
754
|
);
|
|
467
755
|
function readSessionState(sessionId) {
|
|
468
756
|
try {
|
|
469
|
-
const stateFile =
|
|
470
|
-
if (!
|
|
471
|
-
const raw =
|
|
757
|
+
const stateFile = join7(SESSION_STATE_DIR, `${sessionId}.json`);
|
|
758
|
+
if (!existsSync7(stateFile)) return { humanMessageCount: 0 };
|
|
759
|
+
const raw = readFileSync6(stateFile, "utf-8");
|
|
472
760
|
const parsed = JSON.parse(raw);
|
|
473
761
|
return {
|
|
474
762
|
humanMessageCount: typeof parsed.humanMessageCount === "number" ? parsed.humanMessageCount : 0
|
|
@@ -479,17 +767,17 @@ function readSessionState(sessionId) {
|
|
|
479
767
|
}
|
|
480
768
|
function writeSessionState(sessionId, state) {
|
|
481
769
|
try {
|
|
482
|
-
|
|
483
|
-
const stateFile =
|
|
484
|
-
|
|
770
|
+
mkdirSync5(SESSION_STATE_DIR, { recursive: true });
|
|
771
|
+
const stateFile = join7(SESSION_STATE_DIR, `${sessionId}.json`);
|
|
772
|
+
writeFileSync4(stateFile, JSON.stringify(state, null, 2), "utf-8");
|
|
485
773
|
} catch (e) {
|
|
486
774
|
console.error(`STOP-HOOK: Could not write session state: ${e}`);
|
|
487
775
|
}
|
|
488
776
|
}
|
|
489
777
|
function deleteSessionState(sessionId) {
|
|
490
778
|
try {
|
|
491
|
-
const stateFile =
|
|
492
|
-
if (
|
|
779
|
+
const stateFile = join7(SESSION_STATE_DIR, `${sessionId}.json`);
|
|
780
|
+
if (existsSync7(stateFile)) {
|
|
493
781
|
unlinkSync(stateFile);
|
|
494
782
|
}
|
|
495
783
|
} catch {
|
|
@@ -996,7 +1284,7 @@ STOP-HOOK TRIGGERED AT ${timestamp}`);
|
|
|
996
1284
|
}
|
|
997
1285
|
let transcript;
|
|
998
1286
|
try {
|
|
999
|
-
transcript =
|
|
1287
|
+
transcript = readFileSync6(transcriptPath, "utf-8");
|
|
1000
1288
|
debug(`Transcript loaded: ${transcript.split("\n").length} lines`);
|
|
1001
1289
|
} catch (e) {
|
|
1002
1290
|
console.error(`Error reading transcript: ${e}`);
|