devlog-tracker 0.23.0 → 0.25.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # devlog-tracker
2
2
 
3
- **版本** 0.23.0
3
+ **版本** 0.25.0
4
4
 
5
5
  在專案中維護一份 `.devlog/devlog.md`,把每一輪對話的請求、決策與結果寫成永久紀錄。對話一 `/clear` 或換 session 就沒了;這份檔案取代那個缺口,讓工作可以中斷再接。沒下過 `/devlog-tracker:start` 時,裝著也不會動任何檔案。
6
6
 
@@ -46,10 +46,12 @@ npx devlog-tracker init --codex --cursor
46
46
  其他工具已設定的 hook)。重新執行 `npx devlog-tracker init` 可以升級到套件目前的
47
47
  版本;`npx devlog-tracker status` 可以查目前裝的版本是否落後。
48
48
 
49
- 裝 Codex 時,`init` 還會在專案根目錄的 `AGENTS.md` 加上(或更新)一段以
50
- `<!-- devlog-tracker:begin/end -->` 包住的說明,告訴 Codex 沒有 slash 指令時該讀
51
- `.devlog-tracker/commands/*.md`。區塊外的內容不會動,重跑 `init` 只會換掉區塊本身;
52
- 區塊裡只有相對路徑,可以 commit。
49
+ 裝 Codex 時,`init` 會從 `.devlog-tracker/commands/*.md` 產生對應的專案指令到
50
+ `.codex/prompts/`,例如以 `/prompts:devlog-start` 啟動追蹤。也會在專案根目錄的
51
+ `AGENTS.md` 加上(或更新)一段以 `<!-- devlog-tracker:begin/end -->` 包住的 fallback
52
+ 說明。區塊外的內容不會動,重跑 `init` 只會換掉區塊本身;區塊裡只有相對路徑,可以 commit。
53
+
54
+ 裝完 Codex 之後,第一次還要在 Codex 裡核准這些 hook,否則不會記錄(見下方〈Codex(選用)〉的「hook 需要審核」)。
53
55
 
54
56
  `init` 會把這台機器專屬的絕對路徑寫進 `.codex/hooks.json`、`.cursor/hooks.json` 與
55
57
  `.devlog-tracker/env.sh`。如果你把這些檔案 commit 進 git,每位隊友都要在自己的機器上
@@ -87,13 +89,26 @@ Claude Code 仍是主要安裝方式。若要在 Codex CLI 使用,先設定
87
89
  `hooks` 合併進專案(或 `~/.codex/`)的 `hooks.json`。也可以把整個 `codex/hooks/` 與
88
90
  `hooks/scripts/` vendoring 到專案,並調整 command 路徑;兩者的相對目錄必須維持可用。
89
91
 
92
+ **hook 需要審核才會執行。** Codex 對新增或有變動的 hook 要求先審核;沒核准的 hook 會被
93
+ 直接略過,而且**沒有任何警告**,看起來就像 devlog 沒在記錄。這跟專案有沒有設成
94
+ `trust_level = "trusted"` 是兩回事,專案信任不會讓 hook 生效。
95
+
96
+ - 互動模式:第一次開啟時 Codex 會提示有 hook 需要審核,核准後才會執行。之後 hook 的設定
97
+ 有變動(例如重跑 `init` 讓路徑或指令改變)也可能要再核准一次。
98
+ - 非互動的 `codex exec`(CI、腳本):未審核的 hook 會被靜默略過。`--dangerously-bypass-hook-trust`
99
+ 可以讓它們跑起來,但那個旗標會略過所有 hook 的信任檢查,只適合已經自己確認過 hook 來源的
100
+ 自動化環境。
101
+ - 想確認有沒有生效:`start` 之後送一則訊息,看 `.devlog/.round-current.md` 有沒有出現這一輪的
102
+ User Input skeleton;沒有就代表 hook 沒被執行。
103
+
90
104
  Codex 目前沒有對應「使用者中斷」(Claude Code 的 `PostToolUseFailure`/
91
105
  `is_interrupt`)與「這輪異常結束」(`StopFailure`)的事件,這兩種細節狀態在
92
106
  Codex 上不會被標記成 `INTERRUPTED`;核心強制記錄機制(`Stop` 事件擋住未寫完的
93
107
  輪次)不受影響。
94
108
 
95
- Codex 沒有 `/devlog-tracker:*` slash 指令面;hooks 裝好後,請用與 Claude commands
96
- 相同的腳本(`commands/*.md` 會優先讀 `CLAUDE_PLUGIN_ROOT`,否則讀
109
+ Codex 使用 `/prompts:devlog-<名稱>`(例如 `/prompts:devlog-start`)執行指令;它們會
110
+ 讀與 Claude commands 相同的內容。若使用的 Codex 環境沒有載入 custom prompts,仍可直接
111
+ 使用以下腳本(`commands/*.md` 會優先讀 `CLAUDE_PLUGIN_ROOT`,否則讀
97
112
  `DEVLOG_TRACKER_ROOT`):
98
113
 
99
114
  ```bash
package/cli/agents-md.js CHANGED
@@ -8,19 +8,23 @@ const END = '<!-- devlog-tracker:end -->';
8
8
  const BLOCK = `${BEGIN}
9
9
  ## devlog-tracker
10
10
 
11
- 這個專案用 devlog-tracker 在 \`.devlog/devlog.md\` 維護逐輪紀錄(Claude Code 與 Codex 共用同一份)。Codex 沒有 \`/devlog-tracker:*\` slash 指令,請照下面對照做:
11
+ 這個專案用 devlog-tracker 在 \`.devlog/devlog.md\` 維護逐輪紀錄(Claude Code 與 Codex 共用同一份)。Codex 指令在 \`.codex/prompts/\`,以 \`/prompts:devlog-<名稱>\` 執行;若 slash command 不可用,請照下面對照做:
12
12
 
13
13
  1. 先 \`source .devlog-tracker/env.sh\`(設定 \`DEVLOG_TRACKER_ROOT\`)。
14
14
  2. 依使用者意圖讀對應的 \`.devlog-tracker/commands/<名稱>.md\`,照裡面的步驟做(腳本在 \`.devlog-tracker/hooks/scripts/\`,執行時 \`CLAUDE_PROJECT_DIR\` 設成專案根目錄)。
15
15
 
16
16
  | 使用者說 | 讀這份 |
17
17
  |---|---|
18
- | 開始追蹤 / start | \`commands/start.md\` |
18
+ | 開始追蹤 / start | \`/prompts:devlog-start\`;\`commands/start.md\` |
19
+ | 暫停 / pause | \`/prompts:devlog-pause\`;\`commands/pause.md\` |
20
+ | 狀態 / status | \`/prompts:devlog-status\`;\`commands/status.md\` |
19
21
  | 接續上一題 / continue(換過工具或 \`/clear\` 之後) | \`commands/continue.md\` |
20
- | 暫停 / pause | \`commands/pause.md\` |
21
- | 狀態 / status | \`commands/status.md\` |
22
22
  | 歸檔 / compact | \`commands/compact.md\` |
23
- | 保存主題 / keep、接續具名檔 / resume | \`commands/keep.md\`、\`commands/resume.md\` |
23
+ | 清空重編 / clean(不可復原,先問使用者確認) | \`commands/clean.md\` |
24
+ | 保存主題 / keep、接續具名檔 / resume、跨主題總覽 / overview | \`commands/keep.md\`、\`commands/resume.md\`、\`commands/overview.md\` |
25
+ | 長任務定期記錄 / span | \`commands/span.md\` |
26
+ | 調整沉默門檻 / checkpoint、segment-watch | \`commands/checkpoint.md\`、\`commands/segment-watch.md\` |
27
+ | 開發歷程教訓 / lessons、lessons-on、lessons-off、lessons-drift | \`commands/lessons.md\`、\`commands/lessons-on.md\`、\`commands/lessons-off.md\`、\`commands/lessons-drift.md\` |
24
28
 
25
29
  寫 devlog 的格式與規則見 \`.devlog-tracker/skills/devlog-tracker/SKILL.md\`。每輪結束前必須把當輪寫進 \`.devlog/\`;已 \`start\` 的專案,Stop hook 會擋沒寫完的輪次。
26
30
  ${END}
@@ -40,11 +40,27 @@ test('is idempotent and replaces the block in place', () => {
40
40
  assert.equal(once.split(BEGIN).length, 2);
41
41
  });
42
42
 
43
- test('init --codex writes AGENTS.md; --cursor alone does not', async () => {
43
+ test('block mentions every command doc and Codex prompt naming convention', () => {
44
+ const commandsDir = path.join(__dirname, '..', 'commands');
45
+ const text = fs.readFileSync(upsertAgentsMd(tmp()), 'utf8');
46
+ for (const file of fs.readdirSync(commandsDir).filter((f) => f.endsWith('.md'))) {
47
+ assert.ok(text.includes(`commands/${file}`), `AGENTS.md block is missing commands/${file}`);
48
+ }
49
+ assert.ok(text.includes('/prompts:devlog-start'));
50
+ });
51
+
52
+ test('init --codex writes AGENTS.md and custom prompts; --cursor alone does not', async () => {
44
53
  const repoRoot = path.join(__dirname, '..');
45
54
  const withCodex = tmp();
46
55
  await run(['--codex'], { repoRoot, targetDir: withCodex, version: '0.0.0' });
47
56
  assert.ok(fs.existsSync(path.join(withCodex, 'AGENTS.md')));
57
+ for (const file of fs.readdirSync(path.join(repoRoot, 'commands')).filter((file) => file.endsWith('.md'))) {
58
+ const prompt = fs.readFileSync(
59
+ path.join(withCodex, '.codex', 'prompts', `devlog-${path.basename(file, '.md')}.md`),
60
+ 'utf8'
61
+ );
62
+ assert.match(prompt, /\$ARGUMENTS/);
63
+ }
48
64
 
49
65
  const cursorOnly = tmp();
50
66
  await run(['--cursor'], { repoRoot, targetDir: cursorOnly, version: '0.0.0' });
@@ -1,15 +1,33 @@
1
1
  'use strict';
2
+ const fs = require('fs');
2
3
  const path = require('path');
3
4
  const { mergeHooksTemplate } = require('../merge-hooks');
4
5
  const { upsertAgentsMd } = require('../agents-md');
5
6
 
7
+ function installPrompts({ targetDir, vendorRoot }) {
8
+ const commandsDir = path.join(vendorRoot, 'commands');
9
+ const promptsDir = path.join(targetDir, '.codex', 'prompts');
10
+ fs.mkdirSync(promptsDir, { recursive: true });
11
+ for (const file of fs.readdirSync(commandsDir)) {
12
+ if (!file.endsWith('.md')) continue;
13
+ const command = path.basename(file, '.md');
14
+ const source = path.join(commandsDir, file);
15
+ const destination = path.join(promptsDir, `devlog-${command}.md`);
16
+ fs.writeFileSync(
17
+ destination,
18
+ `${fs.readFileSync(source, 'utf8').trimEnd()}\n\n使用者提供的額外參數:$ARGUMENTS\n`
19
+ );
20
+ }
21
+ }
22
+
6
23
  function install({ repoRoot, targetDir, vendorRoot }) {
7
24
  mergeHooksTemplate({
8
25
  templatePath: path.join(repoRoot, 'codex', 'hooks.json'),
9
26
  targetPath: path.join(targetDir, '.codex', 'hooks.json'),
10
27
  vendorRoot,
11
28
  });
29
+ installPrompts({ targetDir, vendorRoot });
12
30
  upsertAgentsMd(targetDir);
13
31
  }
14
32
 
15
- module.exports = { install };
33
+ module.exports = { install, installPrompts };
@@ -66,8 +66,28 @@ json_str_field() {
66
66
  echo
67
67
  return 0
68
68
  fi
69
- raw="$(printf '%s' "$json" | grep -o "\"${key}\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" 2>/dev/null | head -1 || echo '')"
70
- printf '%s' "$raw" | sed -E "s/^.*\"${key}\"[[:space:]]*:[[:space:]]*\"//; s/\"$//"
69
+ # 值要吃得下 \" 這類跳脫序列,否則含引號的 prompt 會在第一個 \" 被截斷;
70
+ # 抓出來之後再還原 \n \t \r \" \\ \/(\uXXXX 不處理,原樣保留)。
71
+ local pat='"'"${key}"'"[[:space:]]*:[[:space:]]*"([^"\\]|\\.)*"'
72
+ raw="$(printf '%s' "$json" | grep -oE "$pat" 2>/dev/null | head -1 || echo '')"
73
+ printf '%s' "$raw" | sed -E "s/^\"${key}\"[[:space:]]*:[[:space:]]*\"//; s/\"$//" | awk '
74
+ BEGIN { ORS = "" }
75
+ {
76
+ s = $0; out = ""
77
+ while (length(s) > 0) {
78
+ c = substr(s, 1, 1)
79
+ if (c == "\\" && length(s) > 1) {
80
+ n = substr(s, 2, 1)
81
+ if (n == "n") out = out "\n"
82
+ else if (n == "t") out = out "\t"
83
+ else if (n == "r") out = out "\r"
84
+ else if (n == "u") out = out "\\u"
85
+ else out = out n
86
+ s = substr(s, 3)
87
+ } else { out = out c; s = substr(s, 2) }
88
+ }
89
+ print out
90
+ }'
71
91
  echo
72
92
  }
73
93
 
@@ -19,6 +19,7 @@ NODE_BIN="$(command -v node)"
19
19
  [ -f "$PROJECT/.devlog-tracker/VERSION" ] && echo "PASS: vendored VERSION exists" || { echo "FAIL: no VERSION"; FAIL=1; }
20
20
  [ -f "$PROJECT/.devlog-tracker/env.sh" ] && echo "PASS: vendored env.sh exists" || { echo "FAIL: no env.sh"; FAIL=1; }
21
21
  [ -f "$PROJECT/.codex/hooks.json" ] && echo "PASS: .codex/hooks.json written" || { echo "FAIL: no .codex/hooks.json"; FAIL=1; }
22
+ [ -f "$PROJECT/.codex/prompts/devlog-start.md" ] && echo "PASS: Codex start prompt written" || { echo "FAIL: no Codex start prompt"; FAIL=1; }
22
23
  [ -f "$PROJECT/.cursor/hooks.json" ] && echo "PASS: .cursor/hooks.json written" || { echo "FAIL: no .cursor/hooks.json"; FAIL=1; }
23
24
 
24
25
  # re-running must not duplicate entries
@@ -49,6 +49,23 @@ assert_eq "str field null" "" "$(json_str_field "$INPUT" reason)"
49
49
  INPUT='not json'
50
50
  assert_eq "str field malformed" "" "$(json_str_field "$INPUT" reason)"
51
51
 
52
+ # Escaped characters must survive with and without jq (the no-jq path is a
53
+ # grep/awk fallback). NOJQ_BIN is a PATH holding only the tools the fallback
54
+ # needs, so `command -v jq` fails inside it.
55
+ NOJQ_BIN="$(mktemp -d)"
56
+ trap 'rm -f "$TMP"; rm -rf "$NOJQ_BIN"' EXIT
57
+ for b in grep sed awk head tr cat; do
58
+ for d in /usr/bin /bin; do [ -x "$d/$b" ] && { ln -s "$d/$b" "$NOJQ_BIN/$b"; break; }; done
59
+ done
60
+ INPUT='{"session_id":"s","prompt":"say \"hi\" \\ back\/slash\ttab 你好","after":"ok"}'
61
+ EXPECT_PROMPT="$(printf 'say "hi" \\ back/slash\ttab 你好')"
62
+ for mode in jq nojq; do
63
+ if [ "$mode" = nojq ]; then P="$NOJQ_BIN"; else P="$PATH"; fi
64
+ [ "$mode" = jq ] && ! command -v jq >/dev/null 2>&1 && continue
65
+ assert_eq "[$mode] escaped quote/backslash/tab prompt" "$EXPECT_PROMPT" "$(PATH="$P" json_str_field "$INPUT" prompt)"
66
+ assert_eq "[$mode] field after escaped one" "ok" "$(PATH="$P" json_str_field "$INPUT" after)"
67
+ done
68
+
52
69
  assert_eq "slugify basic" "foo-bar" "$(slugify 'foo bar')"
53
70
  assert_eq "slugify collapses runs" "foo-bar" "$(slugify 'foo bar')"
54
71
  assert_eq "slugify trims edges" "foo" "$(slugify ' foo ')"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devlog-tracker",
3
- "version": "0.23.0",
3
+ "version": "0.25.0",
4
4
  "description": "npx installer for the devlog-tracker Claude Code plugin's Cursor and Codex adapters.",
5
5
  "bin": {
6
6
  "devlog-tracker": "bin/devlog-tracker.js"