llm-wiki-kit 0.2.16 → 0.2.18

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
@@ -10,6 +10,8 @@ The goal is not to make users run `ingest` or `record` commands. After install,
10
10
 
11
11
  - Korean user prompt -> Korean hook guidance.
12
12
  - English user prompt -> English hook guidance.
13
+ - Codex synthetic plan execution prompts such as `Implement the plan.` are treated as neutral, so they reuse the last real session language or project preference instead of forcing English.
14
+ - Project `.kit-state.json` may set `preferredLanguage` to `ko` or `en` for neutral prompts.
13
15
  - If there is no clear current prompt language, Claude Code `settings.json` `language` is used when present.
14
16
  - If that setting is missing, local `CLAUDE.md` and `AGENTS.md` language signals are used.
15
17
  - The fallback is English.
@@ -100,7 +102,7 @@ Use Claude Code or Codex normally.
100
102
  The installed hooks:
101
103
 
102
104
  - inject functional compact context at session start, instructions loaded, and prompt submit. The hook still uses `wiki/memory.md`, `wiki/index.md`, relevant wiki search results, maintenance signals, update status, and any compact recovery packet; it formats only the useful parts so user-visible hook context does not look like a raw debug dump.
103
- - automatically choose Korean or English hook guidance from the current user prompt, then fall back to Claude Code `language`, local `CLAUDE.md`/`AGENTS.md`, and English.
105
+ - automatically choose Korean or English hook guidance from the current real user prompt, keep Codex synthetic plan execution prompts neutral, then fall back to remembered session language, project `preferredLanguage`, Claude Code `language`, local `CLAUDE.md`/`AGENTS.md`, and English.
104
106
  - remove Codex-facing legacy `oh-my-codex:wiki`/`omx_wiki` surfaces at session start so `llm-wiki/` remains the active wiki implementation
105
107
  - record small redacted raw event envelopes and per-turn state
106
108
  - capture meaningful work and structured decision points, including tool evidence, changed files, verification notes, and reusable durable-candidate signals
@@ -148,7 +150,7 @@ Installed npm runtimes also perform a cached update notice check from hooks whil
148
150
 
149
151
  `llm-wiki consolidate` refreshes only generated marker blocks in `wiki/memory.md` and `wiki/index.md`. Generated maps keep durable non-archived pages, hide default episodic records, skip stale/archived/superseded pages, and report those counts in dry-run output. It is an agent maintenance helper, not a command users should run after every turn.
150
152
 
151
- `llm-wiki maintenance` prints the queue and review due status from `llm-wiki/outputs/maintenance/queue.md`. Queue states are `pending -> approved -> done` or `skipped`. Use `llm-wiki maintenance --workspace <project> --approve <id> --target <wiki/...md>` when durable promotion is approved, `--done <id> --target <wiki/...md>` after the active agent has merged the fact into a durable page, and `--skip <id> [--note "..."]` for duplicate or non-durable candidates. Approved items are shown before pending items in hook reminders. Periodic maintenance is a soft agent-side reminder, not a user command loop.
153
+ `llm-wiki maintenance` prints the queue and review due status from `llm-wiki/outputs/maintenance/queue.md`. Queue states are `pending -> approved -> done` or `skipped`. Use `llm-wiki maintenance --workspace <project> --approve <id> --target <wiki/...md>` when durable promotion is approved, `--done <id> --target <wiki/...md>` after the active agent has merged the fact into a durable page, and `--skip <id> [--note "..."]` for duplicate or non-durable candidates. `llm-wiki maintenance --workspace <project> --apply-lifecycle --dry-run` previews automatic queue hygiene; without `--dry-run`, high-signal or explicit candidates are moved to `approved` once pending reaches the lifecycle threshold, old low-signal candidates may be skipped, and old reviewed items move to `outputs/maintenance/archive/`. Hooks apply that lifecycle only on `SessionStart`/`InstructionsLoaded`; `UserPromptSubmit` only reminds. Approved items are shown before pending items in hook reminders. Periodic maintenance is a soft agent-side reminder, not a user command loop.
152
154
 
153
155
  `llm-wiki eval --workspace <project> [--fixture <path>] [--limit 5] [--json]` runs retrieval fixtures from `llm-wiki/evals/retrieval.json` by default. If the fixture is absent, it exits successfully with `no fixture found`. Fixtures list `query`, `expected`, and `unexpected` paths; output reports recall, missed expected hits, unexpected hits, and top hits using the same durable visibility policy as export.
154
156
 
package/docs/concepts.md CHANGED
@@ -23,9 +23,10 @@ The important behavior is a loop:
23
23
  7. Simple answers, status checks, and keyword-only responses stay out of live Q&A and durable wiki by default.
24
24
  8. Durable wiki promotion is selective: explicit record/document requests and hook-suggested durable candidates should be handled by the active agent in existing wiki pages; the hook queues review only when the turn was not reflected in durable files.
25
25
  9. At the next start/prompt after an abrupt shutdown, hooks can recover stale turn state into `outputs/maintenance/queue.md`.
26
- 10. When reusable knowledge appears, the active Claude Code/Codex agent folds approved or hook-suggested facts into existing durable wiki pages instead of leaving everything as one-off Q&A.
27
- 11. Export and eval reuse the same durable visibility policy so handoff manifests, retrieval fixtures, and context selection describe the same wiki surface.
28
- 12. Future sessions start from the improved wiki instead of relying on long chat history.
26
+ 10. At session start, deterministic lifecycle hygiene may approve explicit/high-signal pending items, skip old low-signal items, and archive old reviewed items without generating wiki pages automatically.
27
+ 11. When reusable knowledge appears, the active Claude Code/Codex agent folds approved or hook-suggested facts into existing durable wiki pages instead of leaving everything as one-off Q&A.
28
+ 12. Export and eval reuse the same durable visibility policy so handoff manifests, retrieval fixtures, and context selection describe the same wiki surface.
29
+ 13. Future sessions start from the improved wiki instead of relying on long chat history.
29
30
 
30
31
  The kit is a template/runtime repository. It must not centralize project wiki contents.
31
32
 
@@ -44,9 +45,9 @@ The maintenance loop is intentionally layered:
44
45
  - `index.md`: broad navigation map.
45
46
  - MiniSearch + wikilinks: retrieval over durable `wiki/**/*.md`, with episodic `wiki/queries/`, `wiki/context/`, and `session-log` pages hidden by default unless promoted or `--include-episodic` is requested; archived/superseded pages stay preserved but hidden unless `--include-archived` is requested. Verbose context explains `why selected`; hook context stays compact.
46
47
  - `evidence_refs`: optional frontmatter that ties durable claims to `file:`, `cmd:`, `raw:`, or `url:` evidence without embedding secrets or raw transcripts.
47
- - `outputs/maintenance/queue.md`: selective reminders for explicit durable requests, hook-suggested durable candidates, and stale turn recovery that need review. Queue state is `pending`, `approved`, `done`, or `skipped`.
48
+ - `outputs/maintenance/queue.md`: selective reminders for explicit durable requests, hook-suggested durable candidates, and stale turn recovery that need review. Queue state is `pending`, `approved`, `done`, or `skipped`; reviewed history can move to `outputs/maintenance/archive/`.
48
49
  - `lint`: finds broken links, stale pages, duplicates, metadata gaps, invalid evidence refs, secret-like content, outdated managed rules, memory/page-count budget pressure, hidden episodic growth, and stale/archived discoverability gaps.
49
- - `maintenance`: reports `reviewDue` only when periodic thresholds are met; hook reminders are soft and limited to session start/instructions loaded or compact prompt-time reminders for maintenance prompts, approved items, durable candidates, stale/recovered items, or review-threshold pressure.
50
+ - `maintenance`: reports `reviewDue` only when periodic thresholds are met; hook reminders are soft and limited to session start/instructions loaded or compact prompt-time reminders for maintenance prompts, approved items, durable candidates, stale/recovered items, or review-threshold pressure. `--apply-lifecycle` previews or applies bounded queue hygiene; it does not merge or create durable wiki pages.
50
51
  - `consolidate`: agent helper that refreshes generated blocks in `memory.md` and `index.md` while preserving handwritten notes, keeping default query/context/session pages out of the durable generated maps, and skipping stale/archived/superseded pages.
51
52
  - `eval`: checks retrieval fixtures in `llm-wiki/evals/retrieval.json` and reports expected recall, missed expected paths, unexpected hits, and top hits.
52
53
  - `export`: writes redacted `llms.txt`, `llms-full.txt`, and `llm-wiki.json` manifests for agent onboarding, handoff, retrieval eval, and external consumption. `llms.txt` is not treated as a passive SEO artifact.
@@ -42,11 +42,11 @@ when no project `CLAUDE.md` exists. Existing `CLAUDE.md` files are not overwritt
42
42
 
43
43
  The hook records redacted turn summaries but does not deny tool calls only because an input looks sensitive. Hook payloads are stored as small redacted event envelopes rather than full transcripts, and context output is redacted field by field before it is returned to Claude Code.
44
44
 
45
- At `SessionStart`/`InstructionsLoaded`, the hook first attempts a safe managed-template refresh, recovers stale turn state into `outputs/maintenance/queue.md`, performs a cached npm update notice check for npm installs, then injects functional compact context. The context still uses `llm-wiki/wiki/memory.md`, `llm-wiki/wiki/index.md`, relevant wiki/search state, operating rules, maintenance signals, passive runtime update status, and managed-template cleanup notes; the hook formats those signals so they are usable if shown in the Claude Code UI. At `UserPromptSubmit`, it recovers stale turn state, searches wiki pages with MiniSearch or substring fallback, expands one-hop wikilinks, redacts context fields, performs the same cached update notice check, and injects the smallest useful functional compact context set. Verbose `llm-wiki context` can explain `why selected`, `rankReason`, `matchedFields`, and `evidenceRefs`, but hook context keeps those details compact. Update notice cache is scoped by npm command, and maintenance reminders are shown for wiki/maintenance prompts, queue topic matches, approved items, durable candidates, stale/recovered items, or review-threshold pressure.
45
+ At `SessionStart`/`InstructionsLoaded`, the hook first attempts a safe managed-template refresh, recovers stale turn state into `outputs/maintenance/queue.md`, applies deterministic maintenance lifecycle hygiene for the current workspace, performs a cached npm update notice check for npm installs, then injects functional compact context. The context still uses `llm-wiki/wiki/memory.md`, `llm-wiki/wiki/index.md`, relevant wiki/search state, operating rules, maintenance signals, passive runtime update status, and managed-template cleanup notes; the hook formats those signals so they are usable if shown in the Claude Code UI. At `UserPromptSubmit`, it recovers stale turn state, searches wiki pages with MiniSearch or substring fallback, expands one-hop wikilinks, redacts context fields, performs the same cached update notice check, and injects the smallest useful functional compact context set. Verbose `llm-wiki context` can explain `why selected`, `rankReason`, `matchedFields`, and `evidenceRefs`, but hook context keeps those details compact. Update notice cache is scoped by npm command, and maintenance reminders are shown for wiki/maintenance prompts, queue topic matches, approved items, durable candidates, stale/recovered items, or review-threshold pressure.
46
46
 
47
- Hook-visible language is selected from the current user prompt first. Korean prompts get Korean guidance, English prompts get English guidance. If no prompt language is clear, the hook checks Claude Code `settings.json` `language` when it exists, then local `CLAUDE.md`/`AGENTS.md` language signals, then English. The kit does not require Claude Code to expose a language setting.
47
+ Hook-visible language is selected from the current real user prompt first. Korean prompts get Korean guidance, English prompts get English guidance. Neutral prompts can reuse remembered session language or project `preferredLanguage`; then the hook checks Claude Code `settings.json` `language` when it exists, local `CLAUDE.md`/`AGENTS.md` language signals, and finally English. The kit does not require Claude Code to expose a language setting.
48
48
 
49
- `PostToolUse` and `PostToolBatch` record redacted tool summaries in the same turn buffer. `PreCompact` classifies the current turn before compaction: simple turns record only a context note, work-evidence, structured-decision, explicit durable, or hook-suggested durable turns write a chunked live Q&A checkpoint, and durable candidates write a maintenance queue item only when no durable wiki update is detected. Queue items may carry safe `evidence_refs` candidates from changed files and verification commands. The checkpoint can include only a bounded redacted transcript tail, never the full raw transcript or raw `transcript_path`. Compaction is not blocked; if checkpoint storage fails, the hook records a compact recovery packet for the next legal context-injection event. `PostCompact` stores the redacted compact summary as a context note and prepares any pending recovery packet without returning model-visible context directly. In the default `answer-first` mode, `SubagentStop` does not create live Q&A, query, decision, or maintenance files. `Stop` and `SessionEnd` append chunked live Q&A only for work-evidence, structured-decision, or durable-candidate turns and do not auto-create `wiki/queries/` or `wiki/decisions/`. If the user explicitly asked to record durable knowledge, or the turn contains reusable architecture/debugging/policy/procedure/decision signals, and no durable wiki update is detected, `Stop`/`SessionEnd` queue a pending maintenance item for agent review. Approved and durable-candidate maintenance items are surfaced as compact soft reminders. `Stop` and `SessionEnd` then clear the per-session turn buffer; `SubagentStop` does not.
49
+ `PostToolUse` and `PostToolBatch` record redacted tool summaries in the same turn buffer. `PreCompact` classifies the current turn before compaction: simple turns record only a context note, work-evidence, structured-decision, explicit durable, or hook-suggested durable turns write a chunked live Q&A checkpoint, and durable candidates write a maintenance queue item only when no durable wiki update is detected. Queue items carry `signal_level` and may carry safe `evidence_refs` candidates from actual repo files and verification commands. The checkpoint can include only a bounded redacted transcript tail, never the full raw transcript or raw `transcript_path`. Compaction is not blocked; if checkpoint storage fails, the hook records a compact recovery packet for the next legal context-injection event. `PostCompact` stores the redacted compact summary as a context note and prepares any pending recovery packet without returning model-visible context directly. In the default `answer-first` mode, `SubagentStop` does not create live Q&A, query, decision, or maintenance files. `Stop` and `SessionEnd` append chunked live Q&A only for work-evidence, structured-decision, or durable-candidate turns and do not auto-create `wiki/queries/` or `wiki/decisions/`. If the user explicitly asked to record durable knowledge, or the turn contains reusable architecture/debugging/policy/procedure/decision signals, and no durable wiki update is detected, `Stop`/`SessionEnd` queue a pending maintenance item for agent review. The lifecycle can move explicit/high-signal items to `approved`, skip stale low-signal items, and archive old reviewed items, but it never creates durable wiki pages automatically. Approved and durable-candidate maintenance items are surfaced as compact soft reminders. `Stop` and `SessionEnd` then clear the per-session turn buffer; `SubagentStop` does not.
50
50
 
51
51
  For handoff or retrieval verification, use `llm-wiki export --workspace <project> --format all` and `llm-wiki eval --workspace <project>`. The generated `llms.txt`/`llms-full.txt`/`llm-wiki.json` files are redacted durable manifests, not raw transcripts.
52
52
 
@@ -29,15 +29,15 @@ Handled events:
29
29
 
30
30
  Expected behavior:
31
31
 
32
- - `SessionStart` first attempts a safe managed-template refresh, removes Codex-facing legacy `oh-my-codex:wiki`/`omx_wiki` surfaces when they reappear, recovers stale turn state into `outputs/maintenance/queue.md`, performs a cached npm update notice check for npm installs, then injects functional compact context. The context still uses `llm-wiki/wiki/memory.md`, `llm-wiki/wiki/index.md`, relevant wiki/search state, operating rules, maintenance signals, passive runtime update status, and managed-template cleanup notes; the hook formats those signals so they are usable if shown in the Codex UI.
32
+ - `SessionStart` first attempts a safe managed-template refresh, removes Codex-facing legacy `oh-my-codex:wiki`/`omx_wiki` surfaces when they reappear, recovers stale turn state into `outputs/maintenance/queue.md`, applies deterministic maintenance lifecycle hygiene for the current workspace, performs a cached npm update notice check for npm installs, then injects functional compact context. The context still uses `llm-wiki/wiki/memory.md`, `llm-wiki/wiki/index.md`, relevant wiki/search state, operating rules, maintenance signals, passive runtime update status, and managed-template cleanup notes; the hook formats those signals so they are usable if shown in the Codex UI.
33
33
  - `UserPromptSubmit` recovers stale turn state, searches project wiki pages with MiniSearch or substring fallback, expands one-hop wikilinks, redacts context fields, performs the same cached update notice check, and injects the smallest useful functional compact context set. Verbose `llm-wiki context` can explain `why selected`, `rankReason`, `matchedFields`, and `evidenceRefs`, but hook context keeps those details compact. Update notice cache is scoped by npm command, and maintenance reminders are shown for wiki/maintenance prompts, queue topic matches, approved items, durable candidates, stale/recovered items, or review-threshold pressure.
34
- - Hook-visible language is selected from the current user prompt first. Korean prompts get Korean guidance, English prompts get English guidance. If no prompt language is clear, Codex falls back to local `CLAUDE.md`/`AGENTS.md` language signals, then English.
34
+ - Hook-visible language is selected from the current real user prompt first. Korean prompts get Korean guidance, English prompts get English guidance. Codex synthetic plan execution prompts such as `Implement the plan.` are treated as neutral, so they reuse remembered session language or project `preferredLanguage` instead of forcing English. If no prompt language is clear, Codex falls back to local `CLAUDE.md`/`AGENTS.md` language signals, then English.
35
35
  - `PreToolUse` records redacted tool summaries without blocking tool calls.
36
36
  - `PostToolUse` records redacted tool summaries in a turn buffer.
37
37
  - `PreCompact` classifies the current turn before compaction. Simple turns record only a context note; work-evidence, structured-decision, explicit durable, or hook-suggested durable turns write a chunked live Q&A checkpoint; durable candidates write a maintenance queue item only when no durable wiki update is detected. The checkpoint can include only a bounded redacted transcript tail, never the full raw transcript or raw `transcript_path`. Compaction is not blocked; if checkpoint storage fails, the hook records a compact recovery packet for the next legal context-injection event.
38
38
  - `PostCompact` stores the redacted compact summary as a context note and prepares any pending compact recovery packet. It does not return `hookSpecificOutput.additionalContext`, because Codex `PostCompact` only supports common output fields.
39
39
  - In the default `answer-first` mode, `SubagentStop` does not create live Q&A, query, decision, or maintenance files. `Stop` appends chunked live Q&A only for work-evidence, structured-decision, or durable-candidate turns and does not auto-create `wiki/queries/` or `wiki/decisions/`.
40
- - If the user explicitly asked to record durable knowledge, or the turn contains reusable architecture/debugging/policy/procedure/decision signals, and no durable wiki update is detected, `Stop` queues a pending maintenance item for agent review. Queue items may carry safe `evidence_refs` candidates from changed files and verification commands. Approved and durable-candidate maintenance items are surfaced as compact soft reminders.
40
+ - If the user explicitly asked to record durable knowledge, or the turn contains reusable architecture/debugging/policy/procedure/decision signals, and no durable wiki update is detected, `Stop` queues a pending maintenance item for agent review. Queue items carry `signal_level` and may carry safe `evidence_refs` candidates from actual repo files and verification commands. The lifecycle can move explicit/high-signal items to `approved`, skip stale low-signal items, and archive old reviewed items, but it never creates durable wiki pages automatically. Approved and durable-candidate maintenance items are surfaced as compact soft reminders.
41
41
  - `Stop` clears the per-session turn buffer after recording. `SubagentStop` leaves the parent turn buffer available for the final stop event.
42
42
 
43
43
  Hook payloads are stored as small redacted event envelopes rather than full transcripts. Context output is also redacted field by field before it is returned to Codex. Functional compact context is a presentation policy, not a feature reduction: Codex still receives the wiki memory, search, maintenance, and passive update signals needed for the hook workflow.
package/docs/manual.md CHANGED
@@ -10,6 +10,8 @@ The runtime supports seamless Korean/English use:
10
10
 
11
11
  - If the current user prompt is clearly Korean, hook-visible guidance is Korean.
12
12
  - If the current user prompt is clearly English, hook-visible guidance is English.
13
+ - Codex synthetic plan execution prompts such as `Implement the plan.` are not treated as English; they reuse the last real session language or project preference.
14
+ - A project can set `preferredLanguage` in `llm-wiki/.kit-state.json` for neutral prompts.
13
15
  - When there is no clear prompt language, Claude Code may use its `settings.json` `language` value when present.
14
16
  - If no setting exists, the runtime checks local `CLAUDE.md` and `AGENTS.md` language signals.
15
17
  - The fallback is English.
@@ -68,13 +70,14 @@ llm-wiki/
68
70
  Use Codex or Claude Code normally. Installed hooks:
69
71
 
70
72
  - inject functional compact context at session start, instructions loaded, and prompt submit;
71
- - select Korean or English hook guidance from the current user prompt and local instruction files;
73
+ - select Korean or English hook guidance from the current real user prompt, remembered session language, project preference, and local instruction files;
72
74
  - use `wiki/memory.md`, `wiki/index.md`, relevant wiki search, maintenance signals, update notices, and compact recovery packets;
73
75
  - record redacted prompt/tool/result summaries in per-turn state;
74
76
  - preserve safe evidence pointers as `evidence_refs` when changed files or verification commands are available;
75
77
  - archive only meaningful work turns, structured decision/debugging turns, or hook-suggested durable candidates into chunked `outputs/questions/YYYY-MM-DD/live-qa-001.md` files;
76
78
  - avoid automatic `wiki/queries/` and `wiki/decisions/` promotion in the default answer-first mode;
77
79
  - queue durable cleanup candidates for explicit documentation requests, hook-suggested durable candidates, or recovered stale turn state that were not reflected in durable wiki files;
80
+ - apply deterministic queue lifecycle hygiene at session start so explicit/high-signal candidates can move to `approved`, stale low-signal candidates can be skipped, and old reviewed items can be archived without creating wiki pages automatically;
78
81
  - refresh clearly managed rules/templates for older projects at session start;
79
82
  - remove legacy Codex-facing `oh-my-codex:wiki`/`omx_wiki` surfaces when they reappear.
80
83
 
@@ -156,7 +159,7 @@ Most users should not need these during daily coding. They are for install, upda
156
159
  - `llm-wiki export --workspace <project> [--format all|llms|llms-full|json] [--output <dir>] [--dry-run] [--json]`: write durable wiki manifests.
157
160
  - `llm-wiki lint --workspace <project>`: wiki health check.
158
161
  - `llm-wiki consolidate --workspace <project> [--dry-run]`: refresh generated blocks in `memory.md` and `index.md`.
159
- - `llm-wiki maintenance --workspace <project> [--approve <id> --target <wiki/...md> | --done <id> --target <wiki/...md> | --skip <id> [--note "..."]] [--json]`: show or update durable cleanup review state.
162
+ - `llm-wiki maintenance --workspace <project> [--apply-lifecycle [--dry-run] | --approve <id> --target <wiki/...md> | --done <id> --target <wiki/...md> | --skip <id> [--note "..."]] [--json]`: show, prioritize, or update durable cleanup review state.
160
163
  - `llm-wiki archive-questions --workspace <project> [--date YYYY-MM-DD] [--dry-run]`: split old flat live Q&A files into chunks.
161
164
  - `llm-wiki uninstall`: remove kit-managed hook entries, leaving project wiki contents intact.
162
165
 
@@ -231,6 +234,7 @@ Missing fixtures exit successfully with `no fixture found`. Present fixtures rep
231
234
  `llm-wiki maintenance` reports queue state and review health. It does not merge pages automatically. The active agent should merge reusable items into existing durable pages and mark queue items through `pending`, `approved`, `done`, or `skipped`.
232
235
 
233
236
  ```bash
237
+ llm-wiki maintenance --workspace <project> --apply-lifecycle --dry-run
234
238
  llm-wiki maintenance --workspace <project> --approve <id> --target wiki/concepts/topic.md
235
239
  llm-wiki maintenance --workspace <project> --done <id> --target wiki/concepts/topic.md
236
240
  llm-wiki maintenance --workspace <project> --skip <id> --note "duplicate"
@@ -238,6 +242,8 @@ llm-wiki maintenance --workspace <project> --skip <id> --note "duplicate"
238
242
 
239
243
  `approved` means durable promotion is accepted but not yet merged. `done` means the durable page has been updated. `skipped` means the item was duplicate or not reusable enough. Approved reminders are shown before pending reminders.
240
244
 
245
+ Lifecycle hygiene applies when pending reaches 15: explicit/high-signal candidates move to `approved`, old low-signal candidates may move to `skipped`, explicit requests and recovered `result_missing` items are not auto-skipped, and old reviewed items beyond the latest 10 move to `outputs/maintenance/archive/YYYY-MM.md`. Hooks apply this only on `SessionStart`/`InstructionsLoaded`; `UserPromptSubmit` only reminds.
246
+
241
247
  Hook reminders are soft:
242
248
 
243
249
  - session start and instructions loaded may show a one-item summary;
@@ -104,6 +104,7 @@ llm-wiki context "search phrase" --workspace /path/to/project
104
104
  llm-wiki lint --workspace /path/to/project
105
105
  llm-wiki consolidate --workspace /path/to/project
106
106
  llm-wiki maintenance --workspace /path/to/project
107
+ llm-wiki maintenance --workspace /path/to/project --apply-lifecycle --dry-run
107
108
  llm-wiki archive-questions --workspace /path/to/project --dry-run
108
109
  ```
109
110
 
@@ -146,7 +147,7 @@ After a plain `npm install -g llm-wiki-kit@latest`, existing hooks keep working
146
147
 
147
148
  Daily use should be Claude Code/Codex first. The user should not need to run a chain of `llm-wiki` commands while working. Hooks inject context automatically, but the current user answer takes priority over wiki cleanup. The active agent updates durable wiki pages when reusable project knowledge appears and the turn's importance or user consent justifies persistence. Hook context policy is function-first: memory, search, maintenance, and update signals remain available, while user-visible context is formatted as functional compact context instead of a raw dump.
148
149
 
149
- In the default `LLM_WIKI_KIT_CAPTURE_MODE=answer-first` mode, `Stop` and `SessionEnd` append live Q&A only for meaningful work evidence, structured decision turns, or reusable durable-candidate signals. Simple answers, status checks, and keyword-only responses are not archived. Live Q&A uses chunked files under `llm-wiki/outputs/questions/YYYY-MM-DD/` and rolls over by line/byte budget. Hooks do not auto-create `wiki/queries/` or `wiki/decisions/`. If the user explicitly asked for recording/documentation, or the turn contains reusable architecture/debugging/policy/procedure/decision signals, and no durable wiki update is detected, a pending cleanup candidate is written to `llm-wiki/outputs/maintenance/queue.md`. `PreCompact` performs the same answer-first classification before context compaction: simple turns get only a context note, archive-worthy turns get a live Q&A checkpoint, and durable candidates get a checkpoint plus queue item only when needed. If checkpoint storage fails, compaction still proceeds and the hook prepares an important-only compact recovery packet for the next legal context-injection event. `SessionStart` and `UserPromptSubmit` also recover stale per-turn state into the same queue when the previous stop hook did not complete. `SessionStart` injects a one-item queue summary; `UserPromptSubmit` injects a compact soft reminder when the prompt is wiki/maintenance related, matches a queue topic, has approved or durable-candidate items, or the queue crosses the review threshold. This is a recovery and reminder layer, not a full transcript capture path.
150
+ In the default `LLM_WIKI_KIT_CAPTURE_MODE=answer-first` mode, `Stop` and `SessionEnd` append live Q&A only for meaningful work evidence, structured decision turns, or reusable durable-candidate signals. Simple answers, status checks, and keyword-only responses are not archived. Live Q&A uses chunked files under `llm-wiki/outputs/questions/YYYY-MM-DD/` and rolls over by line/byte budget. Hooks do not auto-create `wiki/queries/` or `wiki/decisions/`. If the user explicitly asked for recording/documentation, or the turn contains reusable architecture/debugging/policy/procedure/decision signals, and no durable wiki update is detected, a pending cleanup candidate is written to `llm-wiki/outputs/maintenance/queue.md` with a local `signal_level` such as `explicit`, `high`, `low`, or `recovered`. `PreCompact` performs the same answer-first classification before context compaction: simple turns get only a context note, archive-worthy turns get a live Q&A checkpoint, and durable candidates get a checkpoint plus queue item only when needed. If checkpoint storage fails, compaction still proceeds and the hook prepares an important-only compact recovery packet for the next legal context-injection event. `SessionStart` and `UserPromptSubmit` also recover stale per-turn state into the same queue when the previous stop hook did not complete. `SessionStart` injects a one-item queue summary and applies deterministic queue lifecycle hygiene; `UserPromptSubmit` injects a compact soft reminder when the prompt is wiki/maintenance related, matches a queue topic, has approved or durable-candidate items, or the queue crosses the review threshold. This is a recovery and reminder layer, not a full transcript capture path.
150
151
 
151
152
  Use `llm-wiki archive-questions --workspace <project> --dry-run` to review splitting legacy `outputs/questions/YYYY-MM-DD-live-qa.md` files into the chunked layout. Running it without `--dry-run` preserves the original under `outputs/questions/archive/originals/` with a checksum sidecar and replaces the legacy file with a pointer stub.
152
153
 
@@ -209,15 +210,18 @@ Broken links, invalid source IDs, and secret-like content are errors and return
209
210
 
210
211
  Agents may run `consolidate` after meaningful wiki growth. Users should not need to run it after every turn.
211
212
 
212
- `llm-wiki maintenance --workspace <project>` prints queue counts, review due status, and the first pending items. It does not merge wiki pages by itself; the active agent should review pending items, update the closest existing durable wiki document, then mark the queue item `done` or `skipped`. Periodic maintenance is an agent-side task, not something users need to run after every turn.
213
+ `llm-wiki maintenance --workspace <project>` prints queue counts, review due status, and the first pending items. It does not merge wiki pages by itself; the active agent should review pending or approved items, update the closest existing durable wiki document, then mark the queue item `done` or `skipped`. Periodic maintenance is an agent-side task, not something users need to run after every turn.
213
214
 
214
- `llm-wiki maintenance --workspace <project> --json` includes `reviewDue`, `reviewReasons`, `pendingCount`, `stalePendingCount`, `health`, and `recommendedCommands`. Review is due when the last review is older than 14 days, pending queue size reaches 5, stale or result-missing pending items exist, lint has warnings/errors, `memory.md` is near budget, or wiki page count reaches 80% of the search cap. Hook reminders are soft: `SessionStart`/`InstructionsLoaded` may show a short due note, while `UserPromptSubmit` shows one compact item for wiki/maintenance prompts, approved items, durable candidates, stale/recovered items, or review-threshold pressure. The reminder never blocks the current answer.
215
+ `llm-wiki maintenance --workspace <project> --apply-lifecycle --dry-run` previews automatic queue hygiene. Running it without `--dry-run` approves explicit/high-signal pending candidates when pending reaches 15, skips low-signal pending candidates older than 14 days, never auto-skips explicit requests or recovered `result_missing` items, and moves old `done`/`skipped` items beyond the latest 10 into `outputs/maintenance/archive/YYYY-MM.md`. The lifecycle never writes durable wiki pages automatically; it only keeps the review queue bounded and prioritized. Hooks run the same lifecycle on `SessionStart`/`InstructionsLoaded`, while `UserPromptSubmit` only surfaces reminders.
216
+
217
+ `llm-wiki maintenance --workspace <project> --json` includes `reviewDue`, `reviewReasons`, `pendingCount`, `stalePendingCount`, `health`, `recommendedCommands`, and lifecycle action counts when lifecycle is applied. Review is due when the last review is older than 14 days, pending queue size reaches 5, stale or result-missing pending items exist, lint has warnings/errors, `memory.md` is near budget, or wiki page count reaches 80% of the search cap. Hook reminders are soft: `SessionStart`/`InstructionsLoaded` may show a short due note, while `UserPromptSubmit` shows one compact item for wiki/maintenance prompts, approved items, durable candidates, stale/recovered items, or review-threshold pressure. The reminder never blocks the current answer.
215
218
 
216
219
  Recommended agent checklist:
217
220
 
218
221
  ```bash
219
222
  llm-wiki lint --workspace /path/to/project
220
223
  llm-wiki maintenance --workspace /path/to/project
224
+ llm-wiki maintenance --workspace /path/to/project --apply-lifecycle --dry-run
221
225
  llm-wiki consolidate --workspace /path/to/project --dry-run
222
226
  llm-wiki consolidate --workspace /path/to/project
223
227
  ```
@@ -292,6 +296,8 @@ Managed project files are conservative:
292
296
 
293
297
  Real registry checks require `llm-wiki-kit` to be published to npm. Before publication, `llm-wiki update --check` returns npm 404. Use fake npm in tests or local tarball install for server smoke checks.
294
298
 
299
+ For Korean-first Codex projects, set `preferredLanguage: "ko"` in `llm-wiki/.kit-state.json` when neutral prompts should stay Korean. This is especially useful after Codex plan approval, because Codex may submit `Implement the plan.` as the next prompt and the runtime deliberately treats that exact synthetic prompt as language-neutral.
300
+
295
301
  ## Release Checklist
296
302
 
297
303
  Before publishing:
@@ -235,10 +235,11 @@ Check the queue and health warnings:
235
235
 
236
236
  ```bash
237
237
  llm-wiki maintenance --workspace /path/to/project
238
+ llm-wiki maintenance --workspace /path/to/project --apply-lifecycle --dry-run
238
239
  llm-wiki lint --workspace /path/to/project
239
240
  ```
240
241
 
241
- If the queue is always empty during ordinary Q&A, that is normal. If you expected an explicit documentation request or durable candidate to queue, confirm hooks run and that the turn had a captured `UserPromptSubmit`. If pending items stay around, the active agent should merge reusable content into existing durable wiki pages and mark each item `done` or `skipped` without delaying unrelated answers.
242
+ If the queue is always empty during ordinary Q&A, that is normal. If you expected an explicit documentation request or durable candidate to queue, confirm hooks run and that the turn had a captured `UserPromptSubmit`. If pending items stay around, the active agent should merge reusable content into existing durable wiki pages and mark each item `done` or `skipped` without delaying unrelated answers. When pending reaches the lifecycle threshold, `--apply-lifecycle` can promote explicit/high-signal items to `approved`, skip stale low-signal items, and archive old reviewed entries; it does not create durable wiki pages automatically.
242
243
 
243
244
  ## Authentication Values Were Redacted
244
245
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "llm-wiki-kit",
3
- "version": "0.2.16",
3
+ "version": "0.2.18",
4
4
  "description": "Hook-first living Markdown wiki runtime for Codex and Claude Code with Korean/English prompt-aware guidance.",
5
5
  "type": "module",
6
6
  "files": [
@@ -76,6 +76,12 @@ export function hasDetectedDurableWikiChange(entry) {
76
76
  return /(?:^|[\s"'`])(?:llm-wiki\/)?(?:wiki\/(?!queries\/)(?:architecture|debugging|decisions|concepts|entities|sources|context)\/|procedures\/|llm-wiki\/wiki\/(?!queries\/)(?:architecture|debugging|decisions|concepts|entities|sources|context)\/|llm-wiki\/procedures\/)[^\s"'`]+\.md\b/i.test(text);
77
77
  }
78
78
 
79
+ function durableSignalLevel({ durableConclusion, hasFiles, hasVerification, durableSignal }) {
80
+ if (durableConclusion) return 'high';
81
+ if ((hasFiles || hasVerification) && durableSignal) return 'high';
82
+ return 'low';
83
+ }
84
+
79
85
  export function classifyTurn(entry, eventName = '') {
80
86
  if (eventName === 'SubagentStop') {
81
87
  return {
@@ -108,6 +114,7 @@ export function classifyTurn(entry, eventName = '') {
108
114
  archive: true,
109
115
  suggestDurable: false,
110
116
  queueIfMissingDurable: true,
117
+ signalLevel: 'explicit',
111
118
  };
112
119
  }
113
120
 
@@ -134,6 +141,7 @@ export function classifyTurn(entry, eventName = '') {
134
141
  archive: true,
135
142
  suggestDurable: true,
136
143
  queueIfMissingDurable: true,
144
+ signalLevel: durableSignalLevel({ durableConclusion, hasFiles, hasVerification, durableSignal }),
137
145
  };
138
146
  }
139
147
  return {
@@ -150,6 +158,7 @@ export function classifyTurn(entry, eventName = '') {
150
158
  archive: true,
151
159
  suggestDurable: true,
152
160
  queueIfMissingDurable: true,
161
+ signalLevel: 'high',
153
162
  };
154
163
  }
155
164
 
package/src/cli.js CHANGED
@@ -3,7 +3,7 @@ import { resolve } from 'path';
3
3
  import { formatConsolidateResult, runConsolidate } from './consolidate.js';
4
4
  import { handleHook } from './hook.js';
5
5
  import { install, status, uninstall } from './install.js';
6
- import { formatMaintenanceResult, maintenanceSummary, updateMaintenanceItem } from './maintenance.js';
6
+ import { applyMaintenanceLifecycle, formatMaintenanceResult, maintenanceSummary, updateMaintenanceItem } from './maintenance.js';
7
7
  import { bootstrapProject } from './project.js';
8
8
  import { inspectProjectState } from './project-state.js';
9
9
  import { commandForProject, knownProjectRoots, recordProject } from './projects.js';
@@ -92,6 +92,8 @@ function parseOptions(args) {
92
92
  options.check = true;
93
93
  } else if (arg === '--dry-run') {
94
94
  options.dryRun = true;
95
+ } else if (arg === '--apply-lifecycle') {
96
+ options.applyLifecycle = true;
95
97
  } else if (arg === '--limit') {
96
98
  const value = optionValue(arg, i);
97
99
  const limit = Number(value);
@@ -157,7 +159,7 @@ Usage:
157
159
  llm-wiki export --workspace <project> [--format all|llms|llms-full|json] [--output <dir>] [--dry-run] [--json]
158
160
  llm-wiki lint --workspace <project>
159
161
  llm-wiki consolidate --workspace <project> [--dry-run]
160
- llm-wiki maintenance --workspace <project> [--approve <id> --target <wiki/...md> | --done <id> --target <wiki/...md> | --skip <id> [--note "..."]] [--json]
162
+ llm-wiki maintenance --workspace <project> [--apply-lifecycle [--dry-run] | --approve <id> --target <wiki/...md> | --done <id> --target <wiki/...md> | --skip <id> [--note "..."]] [--json]
161
163
  llm-wiki archive-questions --workspace <project> [--date YYYY-MM-DD] [--dry-run] [--json]
162
164
  `);
163
165
  return;
@@ -288,6 +290,7 @@ Usage:
288
290
  if (command === 'maintenance') {
289
291
  const projectRoot = resolve(options.workspace || process.cwd());
290
292
  const actions = [options.approve ? 'approve' : '', options.done ? 'done' : '', options.skip ? 'skip' : ''].filter(Boolean);
293
+ if (options.applyLifecycle && actions.length > 0) throw new Error('maintenance --apply-lifecycle cannot be combined with --approve, --done, or --skip');
291
294
  if (actions.length > 1) throw new Error('maintenance accepts only one of --approve, --done, or --skip');
292
295
  if (actions.length === 1) {
293
296
  const action = actions[0];
@@ -295,7 +298,16 @@ Usage:
295
298
  printJsonOrText(await updateMaintenanceItem(projectRoot, id, action, options), options);
296
299
  return;
297
300
  }
298
- printJsonOrText(await maintenanceSummary(projectRoot, { ...options, includeLint: true }), options, formatMaintenanceResult);
301
+ let lifecycle = null;
302
+ if (options.applyLifecycle) {
303
+ lifecycle = await applyMaintenanceLifecycle(projectRoot, options);
304
+ }
305
+ const summary = await maintenanceSummary(projectRoot, {
306
+ ...options,
307
+ includeLint: true,
308
+ ...(lifecycle || {}),
309
+ });
310
+ printJsonOrText(summary, options, formatMaintenanceResult);
299
311
  return;
300
312
  }
301
313
 
@@ -221,6 +221,7 @@ export async function handlePreCompactCapture(projectRoot, provider, eventName,
221
221
  source: liveQaPath,
222
222
  eventName,
223
223
  reason: 'PreCompact durable candidate checkpoint needs review after context compaction.',
224
+ signalLevel: classification.signalLevel,
224
225
  });
225
226
  if (result.created === false && result.reason) {
226
227
  failures.push(`maintenance queue failed: ${result.reason}`);
@@ -3,6 +3,7 @@ import { appendWikiLog } from './project.js';
3
3
  import { backupFile, exists, readText, writeText } from './fs-utils.js';
4
4
  import { hasSecretLikeText, isSensitivePath, normalizeForStorage } from './redaction.js';
5
5
  import { indexPage, memoryPage } from './templates.js';
6
+ import { resolveLanguage } from './language.js';
6
7
  import { collectWikiPages } from './wiki-model.js';
7
8
  import { runLint } from './wiki-lint.js';
8
9
  import {
@@ -99,22 +100,25 @@ function sortedPages(pages) {
99
100
  });
100
101
  }
101
102
 
102
- function buildGeneratedMemoryBlock(pages, stats) {
103
+ function buildGeneratedMemoryBlock(pages, stats, options = {}) {
104
+ const ko = options.language === 'ko';
103
105
  const allCandidates = sortedPages(pages);
104
106
  const candidates = allCandidates.slice(0, 25);
105
107
  stats.memoryPages = candidates.length;
106
108
  const lines = [
107
- '## Generated Memory Map',
109
+ ko ? '## 생성된 Memory Map' : '## Generated Memory Map',
108
110
  '',
109
- `- Durable pages indexed: ${allCandidates.length}`,
110
- `- Hidden episodic pages: ${stats.hiddenEpisodicPages}`,
111
- `- Skipped archived/stale/superseded pages: ${stats.archivedSkippedPages}/${stats.staleSkippedPages}/${stats.supersededSkippedPages}`,
111
+ ko ? `- 색인된 durable 문서: ${allCandidates.length}` : `- Durable pages indexed: ${allCandidates.length}`,
112
+ ko ? `- 숨긴 episodic 문서: ${stats.hiddenEpisodicPages}` : `- Hidden episodic pages: ${stats.hiddenEpisodicPages}`,
113
+ ko
114
+ ? `- 건너뛴 archived/stale/superseded 문서: ${stats.archivedSkippedPages}/${stats.staleSkippedPages}/${stats.supersededSkippedPages}`
115
+ : `- Skipped archived/stale/superseded pages: ${stats.archivedSkippedPages}/${stats.staleSkippedPages}/${stats.supersededSkippedPages}`,
112
116
  ];
113
117
  if (candidates.length === 0) {
114
- lines.push('- No durable pages found yet.');
118
+ lines.push(ko ? '- 아직 durable 문서가 없습니다.' : '- No durable pages found yet.');
115
119
  return lines.join('\n');
116
120
  }
117
- lines.push('', '### High-Value Pages');
121
+ lines.push('', ko ? '### 핵심 문서' : '### High-Value Pages');
118
122
  for (const page of candidates) {
119
123
  const metadata = [
120
124
  page.type || 'unknown',
@@ -127,7 +131,8 @@ function buildGeneratedMemoryBlock(pages, stats) {
127
131
  return lines.join('\n');
128
132
  }
129
133
 
130
- function buildGeneratedIndexBlock(pages) {
134
+ function buildGeneratedIndexBlock(pages, options = {}) {
135
+ const ko = options.language === 'ko';
131
136
  const groups = new Map();
132
137
  const candidates = sortedPages(pages);
133
138
  for (const page of candidates) {
@@ -137,12 +142,12 @@ function buildGeneratedIndexBlock(pages) {
137
142
  }
138
143
 
139
144
  const lines = [
140
- '## Generated Page Map',
145
+ ko ? '## 생성된 문서 지도' : '## Generated Page Map',
141
146
  '',
142
- `- Durable pages indexed: ${candidates.length}`,
147
+ ko ? `- 색인된 durable 문서: ${candidates.length}` : `- Durable pages indexed: ${candidates.length}`,
143
148
  ];
144
149
  if (groups.size === 0) {
145
- lines.push('- No durable pages found yet.');
150
+ lines.push(ko ? '- 아직 durable 문서가 없습니다.' : '- No durable pages found yet.');
146
151
  return lines.join('\n');
147
152
  }
148
153
  for (const type of [...groups.keys()].sort()) {
@@ -171,6 +176,7 @@ export async function runConsolidate(projectRoot, options = {}) {
171
176
  const lintResult = await runLint(projectRoot, { maxFiles: options.maxFiles || 1000 });
172
177
  const pages = await collectWikiPages(projectRoot, { maxFiles: options.maxFiles || 1000 });
173
178
  const { candidates, stats } = classifyGeneratedPages(pages);
179
+ const language = await resolveLanguage(projectRoot, {}).catch(() => 'en');
174
180
  const changed = [];
175
181
  const skipped = [];
176
182
 
@@ -180,7 +186,7 @@ export async function runConsolidate(projectRoot, options = {}) {
180
186
  memoryPage(),
181
187
  MEMORY_START,
182
188
  MEMORY_END,
183
- buildGeneratedMemoryBlock(candidates, stats),
189
+ buildGeneratedMemoryBlock(candidates, stats, { language }),
184
190
  options,
185
191
  );
186
192
  if (memoryChange?.changed) changed.push(memoryChange.changed);
@@ -192,7 +198,7 @@ export async function runConsolidate(projectRoot, options = {}) {
192
198
  indexPage(),
193
199
  INDEX_START,
194
200
  INDEX_END,
195
- buildGeneratedIndexBlock(candidates),
201
+ buildGeneratedIndexBlock(candidates, { language }),
196
202
  options,
197
203
  );
198
204
  if (indexChange?.changed) changed.push(indexChange.changed);
package/src/evidence.js CHANGED
@@ -1,4 +1,5 @@
1
- import { isAbsolute, relative, sep } from 'path';
1
+ import { statSync } from 'fs';
2
+ import { isAbsolute, join, relative, sep } from 'path';
2
3
  import { extractPathsFromText, hasSecretLikeText, isSensitivePath, redactText, summarizeForStorage } from './redaction.js';
3
4
 
4
5
  export const EVIDENCE_PREFIXES = new Set(['file', 'cmd', 'raw', 'url']);
@@ -51,20 +52,37 @@ function cleanCandidatePath(value) {
51
52
  .trim();
52
53
  }
53
54
 
54
- function isUsefulFileCandidate(value) {
55
+ function hasPatchFragment(value) {
56
+ return /(?:\\n|\/n|@@|^\+|^-)/.test(String(value || ''));
57
+ }
58
+
59
+ function looksLikeNetworkOrCodeToken(value) {
60
+ const text = String(value || '');
61
+ if (text.startsWith('@')) return true;
62
+ if (/^\d{1,3}(?:\.\d{1,3}){3}(?::\d+)?$/.test(text)) return true;
63
+ if (/^[A-Za-z0-9.-]+\.[A-Za-z]{2,}(?::\d+)?(?::\d{1,3}(?:\.\d{1,3}){3})?$/.test(text) && !text.includes('/')) return true;
64
+ if (/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)+$/.test(text) && !text.includes('/')) return true;
65
+ return false;
66
+ }
67
+
68
+ function existingProjectFile(projectRoot, value) {
69
+ if (!projectRoot) return false;
70
+ try {
71
+ const info = statSync(join(projectRoot, value));
72
+ return info.isFile();
73
+ } catch {
74
+ return false;
75
+ }
76
+ }
77
+
78
+ function isUsefulFileCandidate(value, projectRoot = '') {
55
79
  const text = cleanCandidatePath(value);
56
80
  if (!text || text.includes('://') || text.startsWith('cmd:') || text.startsWith('raw:')) return false;
57
81
  if (isSensitivePath(text) || hasSecretLikeText(text)) return false;
58
- return (
59
- text.startsWith('llm-wiki/') ||
60
- text.startsWith('src/') ||
61
- text.startsWith('test/') ||
62
- text.startsWith('docs/') ||
63
- text.startsWith('bin/') ||
64
- text.startsWith('examples/') ||
65
- /^(?:README|AGENTS|CLAUDE|LICENSE|package(?:-lock)?|install)\.[A-Za-z0-9]+$/i.test(text) ||
66
- /\.[A-Za-z0-9]{1,12}$/.test(text)
67
- );
82
+ if (hasPatchFragment(text) || looksLikeNetworkOrCodeToken(text)) return false;
83
+ if (isAbsolute(text) || text.split('/').includes('..')) return false;
84
+ if (projectRoot) return existingProjectFile(projectRoot, text);
85
+ return false;
68
86
  }
69
87
 
70
88
  function relativeProjectPath(projectRoot, value) {
@@ -79,8 +97,7 @@ function relativeProjectPath(projectRoot, value) {
79
97
  function addFileRefs(refs, projectRoot, text) {
80
98
  for (const candidate of extractPathsFromText(text || '')) {
81
99
  const rel = relativeProjectPath(projectRoot, candidate);
82
- if (!isUsefulFileCandidate(rel)) continue;
83
- if (isAbsolute(rel) || rel.split('/').includes('..')) continue;
100
+ if (!isUsefulFileCandidate(rel, projectRoot)) continue;
84
101
  refs.push(`file:${rel}`);
85
102
  }
86
103
  }
package/src/hook.js CHANGED
@@ -2,14 +2,14 @@ import { findProjectRoot } from './fs-utils.js';
2
2
  import { classifyTurn, formatDurableCaptureGuidance, hasDetectedDurableWikiChange, isLegacyEagerCaptureMode } from './capture-policy.js';
3
3
  import { bootstrapProject, appendLiveQa, appendSessionEnvelope, appendWikiLog, buildContextBrief, writeDecisionPage, writeQueryPage } from './project.js';
4
4
  import { consumeCompactRecoveryContext, finalizeCompactRecovery, handlePreCompactCapture, recordPostCompactSummary } from './compact-capture.js';
5
- import { recoverStaleTurnStates, recordMaintenanceForEntry } from './maintenance.js';
5
+ import { applyMaintenanceLifecycle, recoverStaleTurnStates, recordMaintenanceForEntry } from './maintenance.js';
6
6
  import { applyProjectTemplateUpdate, inspectProjectState } from './project-state.js';
7
7
  import { recordProject } from './projects.js';
8
8
  import { summarizeForStorage } from './redaction.js';
9
- import { buildEntryFromState, clearTurnState, rememberQuestion, rememberTool } from './state.js';
9
+ import { buildEntryFromState, clearTurnState, readRememberedLanguage, rememberLanguage, rememberQuestion, rememberTool } from './state.js';
10
10
  import { updateNoticeContext } from './update-notice.js';
11
11
  import { removeLegacyOmxWikiSurfaces } from './legacy-omx-wiki.js';
12
- import { resolveLanguage } from './language.js';
12
+ import { detectTextLanguage, isSyntheticPlanPrompt, resolveLanguage } from './language.js';
13
13
  import { relative } from 'path';
14
14
 
15
15
  async function readStdinJson() {
@@ -30,6 +30,11 @@ function promptText(payload) {
30
30
  return payload.prompt || payload.user_prompt || payload.userPrompt || payload.message || payload.input || '';
31
31
  }
32
32
 
33
+ function storagePromptText(prompt, language) {
34
+ if (!isSyntheticPlanPrompt(prompt)) return prompt;
35
+ return language === 'ko' ? '승인된 계획 실행' : 'Approved plan implementation.';
36
+ }
37
+
33
38
  function toolSummary(payload) {
34
39
  const toolName = payload.tool_name || payload.toolName || payload.name || payload.tool?.name || 'tool';
35
40
  const input = payload.tool_input || payload.toolInput || payload.input || payload.arguments || payload.tool?.input || {};
@@ -110,6 +115,7 @@ async function handleAnswerFirstStop(projectRoot, eventName, payload, entry) {
110
115
  source: liveQaPath,
111
116
  eventName,
112
117
  reason: durableQueueReason(classification),
118
+ signalLevel: classification.signalLevel,
113
119
  }).catch(() => {});
114
120
  }
115
121
  await appendWikiLog(projectRoot, `captured ${eventName}; archive=${relative(projectRoot, liveQaPath)}; classification=${classification.kind}`);
@@ -144,10 +150,17 @@ export async function handleHook(provider, explicitEvent) {
144
150
  const cwd = payload.cwd || process.cwd();
145
151
  const projectRoot = await findProjectRoot(cwd);
146
152
  await bootstrapProject(projectRoot);
153
+ const prompt = eventName === 'UserPromptSubmit' ? promptText(payload) : '';
154
+ const promptLanguage = eventName === 'UserPromptSubmit' ? detectTextLanguage(prompt) : null;
155
+ const rememberedLanguage = await readRememberedLanguage(projectRoot, payload).catch(() => null);
147
156
  const requestLanguage = await resolveLanguage(projectRoot, {
148
157
  provider,
149
- prompt: eventName === 'UserPromptSubmit' ? promptText(payload) : '',
158
+ prompt,
159
+ rememberedLanguage,
150
160
  }).catch(() => 'en');
161
+ if (eventName === 'UserPromptSubmit' && promptLanguage) {
162
+ await rememberLanguage(projectRoot, payload, promptLanguage, 'prompt').catch(() => null);
163
+ }
151
164
  await recordProject(projectRoot, 'hook').catch(() => {});
152
165
  await autoUpdateManagedProject(projectRoot, eventName).catch(() => {});
153
166
  await removeLegacyWikiSurfaces(projectRoot, eventName).catch(() => {});
@@ -155,6 +168,9 @@ export async function handleHook(provider, explicitEvent) {
155
168
  if (eventName === 'SessionStart' || eventName === 'InstructionsLoaded' || eventName === 'UserPromptSubmit') {
156
169
  await recoverStaleTurnStates(projectRoot).catch(() => []);
157
170
  }
171
+ if (eventName === 'SessionStart' || eventName === 'InstructionsLoaded') {
172
+ await applyMaintenanceLifecycle(projectRoot).catch(() => null);
173
+ }
158
174
 
159
175
  if (eventName === 'SessionStart' || eventName === 'InstructionsLoaded') {
160
176
  const recovery = supportsAdditionalContext(provider, eventName)
@@ -171,14 +187,17 @@ export async function handleHook(provider, explicitEvent) {
171
187
  }
172
188
 
173
189
  if (eventName === 'UserPromptSubmit') {
174
- const prompt = promptText(payload);
175
- await rememberQuestion(projectRoot, payload, prompt);
176
- const guidance = formatDurableCaptureGuidance(prompt, { language: requestLanguage });
190
+ const storedPrompt = storagePromptText(prompt, requestLanguage);
191
+ await rememberQuestion(projectRoot, payload, storedPrompt, {
192
+ language: requestLanguage,
193
+ skipIfExisting: isSyntheticPlanPrompt(prompt),
194
+ });
195
+ const guidance = formatDurableCaptureGuidance(storedPrompt, { language: requestLanguage });
177
196
  const recovery = await consumeCompactRecoveryContext(projectRoot, payload).catch(() => '');
178
197
  const context = await hookContext(
179
198
  projectRoot,
180
199
  eventName,
181
- [recovery, await buildContextBrief(projectRoot, eventName, prompt, { language: requestLanguage }), guidance].filter(Boolean).join('\n\n'),
200
+ [recovery, await buildContextBrief(projectRoot, eventName, storedPrompt, { language: requestLanguage }), guidance].filter(Boolean).join('\n\n'),
182
201
  payload,
183
202
  requestLanguage
184
203
  );
@@ -206,7 +225,10 @@ export async function handleHook(provider, explicitEvent) {
206
225
 
207
226
  if (eventName === 'Stop' || eventName === 'SubagentStop' || eventName === 'SessionEnd') {
208
227
  const assistantText = payload.last_assistant_message || payload.response || payload.assistant_response || '';
209
- const entry = await buildEntryFromState(projectRoot, payload, assistantText);
228
+ const entry = {
229
+ ...await buildEntryFromState(projectRoot, payload, assistantText),
230
+ language: requestLanguage,
231
+ };
210
232
  if (isLegacyEagerCaptureMode()) {
211
233
  await handleLegacyEagerStop(projectRoot, eventName, payload, entry);
212
234
  } else {