llm-wiki-kit 0.2.15 → 0.2.17

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
@@ -103,16 +103,16 @@ The installed hooks:
103
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.
104
104
  - remove Codex-facing legacy `oh-my-codex:wiki`/`omx_wiki` surfaces at session start so `llm-wiki/` remains the active wiki implementation
105
105
  - record small redacted raw event envelopes and per-turn state
106
- - capture meaningful work and structured decision points, including tool evidence, changed files, and verification notes
106
+ - capture meaningful work and structured decision points, including tool evidence, changed files, verification notes, and reusable durable-candidate signals
107
107
  - attach safe `evidence_refs` candidates to generated durable candidates when changed files or verification commands are available
108
- - before compaction, classify the current turn and save a redacted checkpoint only for meaningful work, structured decisions, or explicit durable requests; explicit durable candidates also get a maintenance queue item when no durable wiki update is detected
108
+ - before compaction, classify the current turn and save a redacted checkpoint only for meaningful work, structured decisions, explicit durable requests, or suggested durable candidates; durable candidates get a maintenance queue item when no durable wiki update is detected
109
109
  - after compaction, store the redacted compact summary only; if pre-compact preservation failed, prepare a recovery packet for the next legal model-visible context hook
110
110
  - allow tool calls to proceed without secret/PII-based hook blocking
111
- - update chunked `llm-wiki/outputs/questions/YYYY-MM-DD/live-qa-001.md` style archives only for meaningful work or structured decision turns
111
+ - update chunked `llm-wiki/outputs/questions/YYYY-MM-DD/live-qa-001.md` style archives only for meaningful work, structured decision turns, or hook-suggested durable candidates
112
112
  - avoid automatic `wiki/queries/` and `wiki/decisions/` promotion in the default answer-first mode
113
- - queue durable cleanup candidates only for explicit documentation requests that were not reflected in durable wiki files, or when stale turn state is recovered
113
+ - 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
114
114
  - recover stale per-turn state into that queue on the next session start or prompt submit when the previous stop hook did not complete
115
- - nudge the active LLM to fold approved reusable facts into existing wiki pages instead of leaving everything as one-off Q&A
115
+ - nudge the active LLM to fold approved or hook-suggested reusable facts into existing wiki pages instead of leaving everything as one-off Q&A
116
116
  - automatically refresh managed rules/templates for older projects when the current runtime starts a session
117
117
 
118
118
  If you need to think about saving every answer manually, the setup has failed.
@@ -148,7 +148,7 @@ Installed npm runtimes also perform a cached update notice check from hooks whil
148
148
 
149
149
  `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
150
 
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.
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. `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
152
 
153
153
  `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
154
 
package/docs/concepts.md CHANGED
@@ -18,14 +18,15 @@ The important behavior is a loop:
18
18
  2. `memory.md`, `index.md`, and relevant wiki context are injected automatically with an answer-first instruction.
19
19
  3. The user works normally; no extra command loop is required.
20
20
  4. Hooks gather redacted prompt/tool/result summaries.
21
- 5. At stop/session end, hooks append redacted chunked live Q&A only for turns with work evidence or structured decision/debugging conclusions.
21
+ 5. At stop/session end, hooks append redacted chunked live Q&A only for turns with work evidence, structured decision/debugging conclusions, or hook-suggested durable candidates.
22
22
  6. When possible, generated candidates carry safe `evidence_refs` such as changed files, verification commands, raw source IDs, or external URLs.
23
23
  7. Simple answers, status checks, and keyword-only responses stay out of live Q&A and durable wiki by default.
24
- 8. Durable wiki promotion is selective: explicit record/document requests should be handled by the active agent in existing wiki pages; the hook queues review only when such a request was not reflected in durable files.
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 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 that need review, plus stale turn recovery. 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 maintenance-related prompts, with approved items shown before pending items.
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 only when the prompt is wiki/maintenance related or matches a queue topic.
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
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.
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 or structured-decision turns write a chunked live Q&A checkpoint, and explicit 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 or structured-decision turns and do not auto-create `wiki/queries/` or `wiki/decisions/`. If the user explicitly asked to record or document durable knowledge and no durable wiki update is detected, `Stop`/`SessionEnd` queue a pending maintenance item for agent review. Approved maintenance items are shown before pending items in later 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.
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 only when the prompt is wiki/maintenance related or matches a queue topic.
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
+ - `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
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.
35
35
  - `PreToolUse` records redacted tool summaries without blocking tool calls.
36
36
  - `PostToolUse` records redacted tool summaries in a turn buffer.
37
- - `PreCompact` classifies the current turn before compaction. Simple turns record only a context note; work-evidence or structured-decision turns write a chunked live Q&A checkpoint; explicit 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.
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
- - 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 or structured-decision turns and does not auto-create `wiki/queries/` or `wiki/decisions/`.
40
- - If the user explicitly asked to record or document durable knowledge 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 maintenance items are shown before pending items in later reminders.
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 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
@@ -72,9 +72,10 @@ Use Codex or Claude Code normally. Installed hooks:
72
72
  - use `wiki/memory.md`, `wiki/index.md`, relevant wiki search, maintenance signals, update notices, and compact recovery packets;
73
73
  - record redacted prompt/tool/result summaries in per-turn state;
74
74
  - preserve safe evidence pointers as `evidence_refs` when changed files or verification commands are available;
75
- - archive only meaningful work turns or structured decision/debugging turns into chunked `outputs/questions/YYYY-MM-DD/live-qa-001.md` files;
75
+ - 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
76
  - avoid automatic `wiki/queries/` and `wiki/decisions/` promotion in the default answer-first mode;
77
- - queue durable cleanup candidates only for explicit documentation requests that were not reflected in durable wiki files, or when stale turn state is recovered;
77
+ - 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;
78
+ - 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
79
  - refresh clearly managed rules/templates for older projects at session start;
79
80
  - remove legacy Codex-facing `oh-my-codex:wiki`/`omx_wiki` surfaces when they reappear.
80
81
 
@@ -88,7 +89,7 @@ Default:
88
89
  LLM_WIKI_KIT_CAPTURE_MODE=answer-first
89
90
  ```
90
91
 
91
- `answer-first` keeps simple Q&A, status checks, and keyword-only replies out of durable wiki and live Q&A by default. It archives work turns with tool evidence, changed-file evidence, verification, or structured `Decision:`/`Root cause:` style conclusions. Explicit durable requests create maintenance queue candidates only when no durable wiki update is detected.
92
+ `answer-first` keeps simple Q&A, status checks, and keyword-only replies out of durable wiki and live Q&A by default. It archives work turns with tool evidence, changed-file evidence, verification, structured `Decision:`/`Root cause:` style conclusions, or reusable durable-candidate signals. Explicit durable requests and hook-suggested durable candidates create maintenance queue candidates only when no durable wiki update is detected.
92
93
 
93
94
  Deprecated compatibility mode:
94
95
 
@@ -156,7 +157,7 @@ Most users should not need these during daily coding. They are for install, upda
156
157
  - `llm-wiki export --workspace <project> [--format all|llms|llms-full|json] [--output <dir>] [--dry-run] [--json]`: write durable wiki manifests.
157
158
  - `llm-wiki lint --workspace <project>`: wiki health check.
158
159
  - `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.
160
+ - `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
161
  - `llm-wiki archive-questions --workspace <project> [--date YYYY-MM-DD] [--dry-run]`: split old flat live Q&A files into chunks.
161
162
  - `llm-wiki uninstall`: remove kit-managed hook entries, leaving project wiki contents intact.
162
163
 
@@ -231,6 +232,7 @@ Missing fixtures exit successfully with `no fixture found`. Present fixtures rep
231
232
  `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
233
 
233
234
  ```bash
235
+ llm-wiki maintenance --workspace <project> --apply-lifecycle --dry-run
234
236
  llm-wiki maintenance --workspace <project> --approve <id> --target wiki/concepts/topic.md
235
237
  llm-wiki maintenance --workspace <project> --done <id> --target wiki/concepts/topic.md
236
238
  llm-wiki maintenance --workspace <project> --skip <id> --note "duplicate"
@@ -238,10 +240,12 @@ llm-wiki maintenance --workspace <project> --skip <id> --note "duplicate"
238
240
 
239
241
  `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
242
 
243
+ 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.
244
+
241
245
  Hook reminders are soft:
242
246
 
243
247
  - session start and instructions loaded may show a one-item summary;
244
- - prompt submit shows a reminder only when the prompt is wiki/maintenance-related or matches a queue topic.
248
+ - prompt submit shows one compact reminder for wiki/maintenance prompts, queue topic matches, approved items, durable candidates, stale/recovered items, or review-threshold pressure.
245
249
 
246
250
  ## PreCompact
247
251
 
@@ -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 or structured decision turns. 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 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 explicit 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 soft reminder only when the prompt is wiki/maintenance related or matches a queue topic. 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 it only for wiki/maintenance/cleanup-related prompts. 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
  ```
@@ -229,16 +229,17 @@ Check:
229
229
 
230
230
  ## Maintenance Queue Is Empty Or Stale
231
231
 
232
- In the default answer-first mode, `llm-wiki/outputs/maintenance/queue.md` is created only when a user explicitly asked for durable recording/documentation but no durable wiki update was detected, or when `SessionStart`/`UserPromptSubmit` recovers stale per-turn state from a session that did not stop cleanly. It is not expected to grow after every normal `Stop`.
232
+ In the default answer-first mode, `llm-wiki/outputs/maintenance/queue.md` is created when a user explicitly asked for durable recording/documentation, a turn was classified as a hook-suggested durable candidate, or stale per-turn state is recovered, and no durable wiki update was detected. It is not expected to grow after every normal `Stop`.
233
233
 
234
234
  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 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.15",
3
+ "version": "0.2.17",
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
 
@@ -116,10 +123,29 @@ export function classifyTurn(entry, eventName = '') {
116
123
  const hasVerification = Boolean(verification);
117
124
  const hasWorkEvidence = hasWork || hasFiles || hasVerification;
118
125
  const durableConclusion = hasDurableConclusion(text);
126
+ const durableSignal = durableConclusion || hasDurableKeyword(text);
127
+ const durableUpdated = hasDetectedDurableWikiChange(entry);
119
128
 
120
129
  if (hasWorkEvidence) {
130
+ if (durableUpdated) {
131
+ return {
132
+ kind: 'durable-updated',
133
+ archive: true,
134
+ suggestDurable: false,
135
+ queueIfMissingDurable: false,
136
+ };
137
+ }
138
+ if (durableSignal) {
139
+ return {
140
+ kind: 'suggest-durable',
141
+ archive: true,
142
+ suggestDurable: true,
143
+ queueIfMissingDurable: true,
144
+ signalLevel: durableSignalLevel({ durableConclusion, hasFiles, hasVerification, durableSignal }),
145
+ };
146
+ }
121
147
  return {
122
- kind: hasDetectedDurableWikiChange(entry) ? 'durable-updated' : 'work',
148
+ kind: 'work',
123
149
  archive: true,
124
150
  suggestDurable: false,
125
151
  queueIfMissingDurable: false,
@@ -128,10 +154,11 @@ export function classifyTurn(entry, eventName = '') {
128
154
 
129
155
  if (durableConclusion) {
130
156
  return {
131
- kind: 'decision',
157
+ kind: 'suggest-durable',
132
158
  archive: true,
133
- suggestDurable: false,
134
- queueIfMissingDurable: false,
159
+ suggestDurable: true,
160
+ queueIfMissingDurable: true,
161
+ signalLevel: 'high',
135
162
  };
136
163
  }
137
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}`);
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,7 +2,7 @@ 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';
@@ -81,21 +81,36 @@ async function handleLegacyEagerStop(projectRoot, eventName, payload, entry) {
81
81
  }
82
82
  }
83
83
 
84
+ function durableQueueReason(classification) {
85
+ if (classification.kind === 'explicit-durable') {
86
+ return 'Explicit durable documentation request did not have a detected durable wiki update.';
87
+ }
88
+ return 'Suggested durable wiki candidate did not have a detected durable wiki update.';
89
+ }
90
+
91
+ function durableFollowUp(entry, classification) {
92
+ if (!classification.suggestDurable) return entry.followUp;
93
+ const message = 'This turn is a durable wiki candidate. After the current answer, merge reusable facts into an existing durable wiki page or mark the maintenance item skipped.';
94
+ const current = String(entry.followUp || '').trim();
95
+ if (!current || current === '(not captured)') return message;
96
+ if (current.includes(message)) return current;
97
+ return `${current}\n${message}`;
98
+ }
99
+
84
100
  async function handleAnswerFirstStop(projectRoot, eventName, payload, entry) {
85
101
  const classification = classifyTurn(entry, eventName);
86
102
  if (classification.archive) {
87
103
  const archiveEntry = {
88
104
  ...entry,
89
- followUp: classification.suggestDurable
90
- ? 'This turn may be worth preserving. If the user approves, merge it into an existing durable wiki page.'
91
- : entry.followUp,
105
+ followUp: durableFollowUp(entry, classification),
92
106
  };
93
107
  const liveQaPath = await appendLiveQa(projectRoot, archiveEntry);
94
108
  if (classification.queueIfMissingDurable && !hasDetectedDurableWikiChange(entry)) {
95
109
  await recordMaintenanceForEntry(projectRoot, entry, {
96
110
  source: liveQaPath,
97
111
  eventName,
98
- reason: 'Explicit durable documentation request did not have a detected durable wiki update.',
112
+ reason: durableQueueReason(classification),
113
+ signalLevel: classification.signalLevel,
99
114
  }).catch(() => {});
100
115
  }
101
116
  await appendWikiLog(projectRoot, `captured ${eventName}; archive=${relative(projectRoot, liveQaPath)}; classification=${classification.kind}`);
@@ -141,6 +156,9 @@ export async function handleHook(provider, explicitEvent) {
141
156
  if (eventName === 'SessionStart' || eventName === 'InstructionsLoaded' || eventName === 'UserPromptSubmit') {
142
157
  await recoverStaleTurnStates(projectRoot).catch(() => []);
143
158
  }
159
+ if (eventName === 'SessionStart' || eventName === 'InstructionsLoaded') {
160
+ await applyMaintenanceLifecycle(projectRoot).catch(() => null);
161
+ }
144
162
 
145
163
  if (eventName === 'SessionStart' || eventName === 'InstructionsLoaded') {
146
164
  const recovery = supportsAdditionalContext(provider, eventName)
@@ -13,7 +13,11 @@ const DEFAULT_STALE_PENDING_DAYS = 7;
13
13
  const DEFAULT_PENDING_LIMIT = 20;
14
14
  const DEFAULT_REVIEW_PENDING_LIMIT = 5;
15
15
  const DEFAULT_REVIEW_INTERVAL_DAYS = 14;
16
+ const DEFAULT_LIFECYCLE_PENDING_THRESHOLD = 15;
17
+ const DEFAULT_LOW_SIGNAL_SKIP_DAYS = 14;
18
+ const DEFAULT_REVIEWED_KEEP = 10;
16
19
  const MEMORY_NEAR_BUDGET_BYTES = 20 * 1024;
20
+ const SIGNAL_LEVELS = new Set(['explicit', 'high', 'low', 'recovered']);
17
21
 
18
22
  function queuePath(projectRoot) {
19
23
  return join(projectRoot, MAINTENANCE_QUEUE_REL);
@@ -26,6 +30,7 @@ function queueHeader() {
26
30
  'Candidates to merge into durable wiki pages. Hooks only create candidates; the active agent reviews and merges them into existing durable wiki documents.',
27
31
  '',
28
32
  'Status values: pending, approved, done, skipped.',
33
+ 'Lifecycle keeps explicit/high-signal candidates visible, may skip old low-signal candidates, and archives old reviewed items.',
29
34
  '',
30
35
  ].join('\n');
31
36
  }
@@ -55,6 +60,44 @@ function sanitizeField(value, maxLength = 500) {
55
60
  return summarizeForStorage(String(value || '').replace(/\s+/g, ' ').trim(), maxLength);
56
61
  }
57
62
 
63
+ function normalizeSignalLevel(value) {
64
+ const text = String(value || '').trim().toLowerCase();
65
+ return SIGNAL_LEVELS.has(text) ? text : '';
66
+ }
67
+
68
+ function inferSignalLevelFromReason(reason, resultMissing = false) {
69
+ const text = String(reason || '').toLowerCase();
70
+ if (resultMissing || text.includes('recovered from stale turn state')) return 'recovered';
71
+ if (text.includes('explicit durable documentation request')) return 'explicit';
72
+ if (text.includes('precompact durable candidate')) return 'high';
73
+ if (text.includes('suggested durable wiki candidate')) return 'high';
74
+ if (text.includes('durable wiki review')) return 'high';
75
+ return 'high';
76
+ }
77
+
78
+ function signalLevelForItem(item) {
79
+ if (item?.result_missing) return 'recovered';
80
+ return normalizeSignalLevel(item?.signal_level || item?.fields?.signal_level) ||
81
+ inferSignalLevelFromReason(item?.reason || item?.fields?.reason, item?.result_missing);
82
+ }
83
+
84
+ function isStaleByDays(item, days, nowMs = Date.now()) {
85
+ const time = Date.parse(item?.created_at || item?.last_seen_at || '');
86
+ return Number.isFinite(time) && nowMs - time >= days * 24 * 60 * 60 * 1000;
87
+ }
88
+
89
+ function reviewedTimeMs(item) {
90
+ const values = [item?.reviewed_at, item?.last_seen_at, item?.created_at]
91
+ .map((value) => Date.parse(value || ''))
92
+ .filter(Number.isFinite);
93
+ return values.length > 0 ? Math.max(...values) : 0;
94
+ }
95
+
96
+ function archiveMonth(item) {
97
+ const time = reviewedTimeMs(item) || Date.now();
98
+ return new Date(time).toISOString().slice(0, 7);
99
+ }
100
+
58
101
  function inferSuggestedTarget(entry, source) {
59
102
  const text = `${entry?.question || ''}\n${entry?.result || ''}\n${entry?.work || ''}\n${source || ''}`.toLowerCase();
60
103
  if (String(source || '').includes('wiki/decisions/') || /decision|decided|결정|선택|채택|확정/.test(text)) return 'wiki/decisions/';
@@ -78,8 +121,10 @@ function itemBlock(item) {
78
121
  last_seen_at: item.last_seen_at,
79
122
  source: item.source,
80
123
  suggested_target: item.suggested_target,
124
+ signal_level: item.signal_level,
81
125
  target: item.target,
82
126
  reviewed_at: item.reviewed_at,
127
+ archived_at: item.archived_at,
83
128
  review_note: item.review_note,
84
129
  evidence_refs: normalizeEvidenceRefs(item.evidence_refs).length > 0
85
130
  ? JSON.stringify(normalizeEvidenceRefs(item.evidence_refs))
@@ -93,8 +138,10 @@ function itemBlock(item) {
93
138
  'last_seen_at',
94
139
  'source',
95
140
  'suggested_target',
141
+ 'signal_level',
96
142
  'target',
97
143
  'reviewed_at',
144
+ 'archived_at',
98
145
  'review_note',
99
146
  'evidence_refs',
100
147
  'reason',
@@ -159,6 +206,7 @@ export async function readMaintenanceQueue(projectRoot) {
159
206
  evidence_refs: parseEvidenceRefsField(item.fields.evidence_refs),
160
207
  evidenceRefs: parseEvidenceRefsField(item.fields.evidence_refs),
161
208
  result_missing: String(item.fields.result_missing || '').toLowerCase() === 'true',
209
+ signal_level: normalizeSignalLevel(item.fields.signal_level),
162
210
  })),
163
211
  };
164
212
  }
@@ -175,6 +223,168 @@ async function writeMaintenanceQueue(projectRoot, items) {
175
223
  return path;
176
224
  }
177
225
 
226
+ function lifecycleDefaults(options = {}) {
227
+ return {
228
+ pendingThreshold: options.pendingThreshold ?? DEFAULT_LIFECYCLE_PENDING_THRESHOLD,
229
+ lowSignalSkipDays: options.lowSignalSkipDays ?? DEFAULT_LOW_SIGNAL_SKIP_DAYS,
230
+ reviewedKeep: options.reviewedKeep ?? DEFAULT_REVIEWED_KEEP,
231
+ };
232
+ }
233
+
234
+ function lifecycleAction(action, item, extra = {}) {
235
+ return {
236
+ action,
237
+ id: item.id,
238
+ topic: item.topic,
239
+ status: item.status,
240
+ signal_level: signalLevelForItem(item),
241
+ source: item.source,
242
+ ...extra,
243
+ };
244
+ }
245
+
246
+ async function appendArchivedItems(projectRoot, items, now) {
247
+ const grouped = new Map();
248
+ for (const item of items) {
249
+ const month = archiveMonth(item);
250
+ if (!grouped.has(month)) grouped.set(month, []);
251
+ grouped.get(month).push(item);
252
+ }
253
+
254
+ const files = [];
255
+ for (const [month, monthItems] of grouped.entries()) {
256
+ const path = join(projectRoot, 'llm-wiki', 'outputs', 'maintenance', 'archive', `${month}.md`);
257
+ const existing = await readText(path, '');
258
+ const header = existing ? '' : [
259
+ '# LLM Wiki Maintenance Archive',
260
+ '',
261
+ `Reviewed queue items archived for ${month}.`,
262
+ '',
263
+ ].join('\n');
264
+ const blocks = [];
265
+ for (const item of monthItems) {
266
+ if (existing.includes(`- id: ${item.id}`)) continue;
267
+ blocks.push(itemBlock({
268
+ ...item,
269
+ fields: { ...(item.fields || {}) },
270
+ archived_at: now,
271
+ }).trimEnd());
272
+ }
273
+ if (blocks.length === 0) continue;
274
+ await appendText(path, `${header}${existing && !existing.endsWith('\n') ? '\n' : ''}${blocks.join('\n\n')}\n`);
275
+ files.push(path);
276
+ }
277
+ return files;
278
+ }
279
+
280
+ export async function applyMaintenanceLifecycle(projectRoot, options = {}) {
281
+ const queue = await readMaintenanceQueue(projectRoot);
282
+ const defaults = lifecycleDefaults(options);
283
+ const now = nowIso();
284
+ const actions = [];
285
+ const pending = queue.items.filter((item) => item.status === 'pending');
286
+ let nextItems = queue.items.map((item) => ({ ...item, fields: { ...(item.fields || {}) } }));
287
+ let approvedByLifecycle = 0;
288
+ let skippedByLifecycle = 0;
289
+ let archivedReviewedCount = 0;
290
+ const shouldApplyPendingLifecycle = pending.length >= defaults.pendingThreshold;
291
+
292
+ if (shouldApplyPendingLifecycle) {
293
+ nextItems = nextItems.map((item) => {
294
+ if (item.status !== 'pending') return item;
295
+ const signal = signalLevelForItem(item);
296
+ if (signal === 'explicit' || signal === 'high') {
297
+ approvedByLifecycle += 1;
298
+ actions.push(lifecycleAction('approve', item, { reason: `pending threshold ${defaults.pendingThreshold} reached` }));
299
+ const target = item.target || item.suggested_target || '';
300
+ const reviewNote = sanitizeField('Auto-approved by lifecycle because the pending queue reached the review threshold.', 500);
301
+ return {
302
+ ...item,
303
+ status: 'approved',
304
+ signal_level: signal,
305
+ target,
306
+ reviewed_at: now,
307
+ last_seen_at: now,
308
+ review_note: item.review_note || reviewNote,
309
+ fields: {
310
+ ...(item.fields || {}),
311
+ signal_level: signal,
312
+ ...(target ? { target } : {}),
313
+ reviewed_at: now,
314
+ last_seen_at: now,
315
+ review_note: item.review_note || reviewNote,
316
+ },
317
+ };
318
+ }
319
+ if (signal === 'low' && isStaleByDays(item, defaults.lowSignalSkipDays)) {
320
+ skippedByLifecycle += 1;
321
+ actions.push(lifecycleAction('skip', item, { reason: `low-signal item older than ${defaults.lowSignalSkipDays} days` }));
322
+ const reviewNote = sanitizeField(`Auto-skipped by lifecycle because this low-signal candidate was older than ${defaults.lowSignalSkipDays} days.`, 500);
323
+ return {
324
+ ...item,
325
+ status: 'skipped',
326
+ signal_level: signal,
327
+ reviewed_at: now,
328
+ last_seen_at: now,
329
+ review_note: item.review_note || reviewNote,
330
+ fields: {
331
+ ...(item.fields || {}),
332
+ signal_level: signal,
333
+ reviewed_at: now,
334
+ last_seen_at: now,
335
+ review_note: item.review_note || reviewNote,
336
+ },
337
+ };
338
+ }
339
+ return {
340
+ ...item,
341
+ signal_level: signal,
342
+ fields: {
343
+ ...(item.fields || {}),
344
+ signal_level: signal,
345
+ },
346
+ };
347
+ });
348
+ }
349
+
350
+ const reviewed = nextItems
351
+ .filter((item) => item.status === 'done' || item.status === 'skipped')
352
+ .sort((a, b) => reviewedTimeMs(b) - reviewedTimeMs(a));
353
+ const reviewedToArchive = reviewed.slice(defaults.reviewedKeep);
354
+ if (reviewedToArchive.length > 0) {
355
+ archivedReviewedCount = reviewedToArchive.length;
356
+ for (const item of reviewedToArchive) {
357
+ actions.push(lifecycleAction('archive-reviewed', item, { reason: `keeping latest ${defaults.reviewedKeep} reviewed items in queue.md` }));
358
+ }
359
+ const archiveIds = new Set(reviewedToArchive.map((item) => item.id));
360
+ nextItems = nextItems.filter((item) => !archiveIds.has(item.id));
361
+ }
362
+
363
+ if (!options.dryRun && actions.length > 0) {
364
+ if (reviewedToArchive.length > 0) {
365
+ await appendArchivedItems(projectRoot, reviewedToArchive, now);
366
+ }
367
+ if (actions.length > 0 || queue.exists) {
368
+ await writeMaintenanceQueue(projectRoot, nextItems);
369
+ }
370
+ }
371
+
372
+ return {
373
+ workspace: projectRoot,
374
+ path: queue.path,
375
+ dryRun: Boolean(options.dryRun),
376
+ appliedPendingLifecycle: shouldApplyPendingLifecycle,
377
+ pendingThreshold: defaults.pendingThreshold,
378
+ lowSignalSkipDays: defaults.lowSignalSkipDays,
379
+ reviewedKeep: defaults.reviewedKeep,
380
+ lifecycleActions: actions,
381
+ actionCount: actions.length,
382
+ approvedByLifecycle,
383
+ skippedByLifecycle,
384
+ archivedReviewedCount,
385
+ };
386
+ }
387
+
178
388
  export function summarizeMaintenanceQueue(queue, options = {}) {
179
389
  const staleDays = options.staleDays ?? DEFAULT_STALE_PENDING_DAYS;
180
390
  const pendingLimit = options.pendingLimit ?? DEFAULT_PENDING_LIMIT;
@@ -240,6 +450,10 @@ export async function maintenanceSummary(projectRoot, options = {}) {
240
450
  reviewReasons,
241
451
  health,
242
452
  recommendedCommands: recommendedMaintenanceCommands(projectRoot),
453
+ lifecycleActions: options.lifecycleActions || [],
454
+ approvedByLifecycle: options.approvedByLifecycle || 0,
455
+ skippedByLifecycle: options.skippedByLifecycle || 0,
456
+ archivedReviewedCount: options.archivedReviewedCount || 0,
243
457
  };
244
458
  }
245
459
 
@@ -247,6 +461,7 @@ function recommendedMaintenanceCommands(projectRoot) {
247
461
  return [
248
462
  `llm-wiki lint --workspace ${projectRoot}`,
249
463
  `llm-wiki maintenance --workspace ${projectRoot}`,
464
+ `llm-wiki maintenance --workspace ${projectRoot} --apply-lifecycle --dry-run`,
250
465
  `llm-wiki consolidate --workspace ${projectRoot} --dry-run`,
251
466
  `llm-wiki consolidate --workspace ${projectRoot}`,
252
467
  ];
@@ -345,6 +560,9 @@ export async function recordMaintenanceForEntry(projectRoot, entry, options = {}
345
560
  if (!source && !options.resultMissing) return { created: false, reason: 'missing-source' };
346
561
  if (!entry?.question || entry.question === '(not captured)') return { created: false, reason: 'missing-question' };
347
562
  const created = nowIso();
563
+ const reason = sanitizeField(options.reason || `Captured ${options.eventName || 'turn'} needs durable wiki review.`, 300);
564
+ const signalLevel = normalizeSignalLevel(options.signalLevel) ||
565
+ inferSignalLevelFromReason(reason, Boolean(options.resultMissing));
348
566
  const item = {
349
567
  id: itemId(projectRoot, source, entry),
350
568
  status: 'pending',
@@ -353,8 +571,9 @@ export async function recordMaintenanceForEntry(projectRoot, entry, options = {}
353
571
  last_seen_at: created,
354
572
  source,
355
573
  suggested_target: options.suggestedTarget || inferSuggestedTarget(entry, source),
574
+ signal_level: signalLevel,
356
575
  evidence_refs: evidenceRefsFromEntry(entry, { projectRoot }),
357
- reason: sanitizeField(options.reason || `Captured ${options.eventName || 'turn'} needs durable wiki review.`, 300),
576
+ reason,
358
577
  result_missing: Boolean(options.resultMissing),
359
578
  };
360
579
  return appendMaintenanceItem(projectRoot, item);
@@ -395,6 +614,7 @@ export async function recoverStaleTurnStates(projectRoot, options = {}) {
395
614
  eventName: 'recovered stale turn state',
396
615
  reason: 'Recovered from stale turn state because Stop/SessionEnd did not complete.',
397
616
  resultMissing: true,
617
+ signalLevel: 'recovered',
398
618
  });
399
619
  await unlink(path).catch(() => {});
400
620
  output.push({ ...result, statePath: path, session: state.session });
@@ -402,6 +622,21 @@ export async function recoverStaleTurnStates(projectRoot, options = {}) {
402
622
  return output;
403
623
  }
404
624
 
625
+ function isDurablePromotionCandidate(item) {
626
+ if (item?.status === 'approved') return true;
627
+ const text = `${item?.reason || ''} ${item?.fields?.reason || ''}`.toLowerCase();
628
+ return /suggested durable wiki candidate|precompact durable candidate|explicit durable documentation|durable wiki review/.test(text);
629
+ }
630
+
631
+ function shouldSurfacePromptMaintenance(summary, query, reviewCandidates) {
632
+ if (isMaintenanceRelatedQuery(query || '', reviewCandidates)) return true;
633
+ if ((summary.approvedCount || 0) > 0) return true;
634
+ if (reviewCandidates.some(isDurablePromotionCandidate)) return true;
635
+ if ((summary.pendingCount || 0) >= (summary.reviewPendingLimit || DEFAULT_REVIEW_PENDING_LIMIT)) return true;
636
+ if ((summary.stalePendingCount || 0) > 0) return true;
637
+ return reviewCandidates.some((item) => item.result_missing);
638
+ }
639
+
405
640
  export function formatMaintenanceContext(summary, options = {}) {
406
641
  if (!summary.reviewDue) return '';
407
642
  const language = options.language === 'ko' ? 'ko' : 'en';
@@ -412,7 +647,7 @@ export function formatMaintenanceContext(summary, options = {}) {
412
647
  let pending = reviewCandidates.slice(0, limit);
413
648
 
414
649
  if (eventName === 'UserPromptSubmit') {
415
- if (!isMaintenanceRelatedQuery(options.query || '', reviewCandidates)) return '';
650
+ if (!shouldSurfacePromptMaintenance(summary, options.query || '', reviewCandidates)) return '';
416
651
  pending = reviewCandidates.slice(0, 1);
417
652
  } else if (eventName !== 'SessionStart' && eventName !== 'InstructionsLoaded') {
418
653
  return '';
@@ -423,12 +658,12 @@ export function formatMaintenanceContext(summary, options = {}) {
423
658
  ? [
424
659
  'LLM Wiki maintenance status:',
425
660
  `- review due: yes (${(summary.reviewReasons || []).slice(0, 2).join('; ') || 'periodic review threshold met'}).`,
426
- `- approved/pending review items: ${reviewCount}. 현재 요청이 우선이며, 관련 있을 때만 durable wiki 정리에 사용한다.`,
661
+ `- approved/pending review items: ${reviewCount}. 현재 요청이 우선이며, 관련 있거나 세션 종료 전 여유가 있으면 durable wiki 병합하거나 skipped로 표시한다.`,
427
662
  ]
428
663
  : [
429
664
  'LLM Wiki maintenance status:',
430
665
  `- review due: yes (${(summary.reviewReasons || []).slice(0, 2).join('; ') || 'periodic review threshold met'}).`,
431
- `- approved/pending review items: ${reviewCount}. The current request comes first; use this only when it is relevant to durable wiki cleanup.`,
666
+ `- approved/pending review items: ${reviewCount}. The current request comes first; when relevant or before ending the session, merge candidates into durable wiki pages or mark them skipped.`,
432
667
  ];
433
668
  for (const item of pending) {
434
669
  const target = item.target || item.suggested_target;
@@ -454,6 +689,14 @@ export function formatMaintenanceResult(summary) {
454
689
  if ((summary.reviewReasons || []).length > 0) {
455
690
  lines.push(`- review reasons: ${summary.reviewReasons.join('; ')}`);
456
691
  }
692
+ if ((summary.lifecycleActions || []).length > 0) {
693
+ lines.push(
694
+ `- lifecycle actions: ${summary.lifecycleActions.length}`,
695
+ `- lifecycle approved: ${summary.approvedByLifecycle || 0}`,
696
+ `- lifecycle skipped: ${summary.skippedByLifecycle || 0}`,
697
+ `- lifecycle archived reviewed: ${summary.archivedReviewedCount || 0}`
698
+ );
699
+ }
457
700
  if ((summary.approved || []).length > 0) {
458
701
  lines.push('', 'Approved:');
459
702
  for (const item of summary.approved.slice(0, 10)) {
@@ -254,7 +254,7 @@ function legacyProcedureSignals(name) {
254
254
  const common = [/^# .+ Procedure/m];
255
255
  const byName = {
256
256
  'ingest.md': [/^# Ingest Procedure/m, /wiki\/memory\.md/, /raw\/inbox|raw\/sources/],
257
- 'query.md': [/^# Query Procedure/m, /llm-wiki context/, /Save reusable answers|reusable answers/],
257
+ 'query.md': [/^# Query Procedure/m, /llm-wiki context/, /Save reusable answers|reusable answers|Merge reusable facts|reusable facts/],
258
258
  'lint.md': [/^# Lint Procedure/m, /llm-wiki lint/, /stale pages|orphan pages/],
259
259
  'security.md': [/^# Security Procedure/m, /Redact authentication values|token|private keys/, /raw transcript capture/],
260
260
  };
@@ -359,12 +359,19 @@ function hasKitManagedSignal(text, descriptor) {
359
359
  return false;
360
360
  }
361
361
 
362
+ function hasPatchableGeneratedSignal(text, descriptor) {
363
+ if (!hasLegacyGeneratedSignal(text, descriptor)) return false;
364
+ if (String(descriptor.id || '').startsWith('procedure-') && /^##\s+/m.test(text)) return false;
365
+ return true;
366
+ }
367
+
362
368
  function canPatchDescriptor(state, descriptor, currentText, fileExists = true) {
363
369
  if (descriptor.mode === 'create-only') return !fileExists;
364
370
  if (descriptor.mode === 'marker') return replaceMarkedBlock(currentText, descriptor.content) !== null;
365
371
  return isRecordedManaged(state, descriptor, currentText) ||
366
372
  isKnownGeneratedContent(currentText, descriptor) ||
367
- isLegacyGeneratedContent(currentText, descriptor);
373
+ isLegacyGeneratedContent(currentText, descriptor) ||
374
+ hasPatchableGeneratedSignal(currentText, descriptor);
368
375
  }
369
376
 
370
377
  function desiredTextForDescriptor(descriptor, currentText, fileExists = true) {
package/src/templates.js CHANGED
@@ -16,9 +16,9 @@ This repository uses llm-wiki-kit as a hook-first living Markdown wiki for Codex
16
16
  - Keep \`llm-wiki/wiki/memory.md\` short. Link to important documents instead of copying long explanations.
17
17
  - Use hook-injected context when helpful, but answer the current user request first. Use \`llm-wiki context\`, \`llm-wiki lint\`, and \`llm-wiki consolidate\` only as agent maintenance helpers.
18
18
  - Hooks safely store redacted raw envelopes and work/decision-focused live Q&A. Simple answers, status checks, and keyword-only turns should not be promoted to live Q&A or durable wiki by default.
19
- - Hooks may queue durable cleanup candidates in \`llm-wiki/outputs/maintenance/queue.md\` at stop/start boundaries. Treat maintenance as a soft agent-side reminder; merge pending items only when relevant and mark them \`done\` or \`skipped\`.
19
+ - Hooks may queue durable cleanup candidates in \`llm-wiki/outputs/maintenance/queue.md\` at stop/start boundaries. Session-start lifecycle may move explicit/high-signal candidates to \`approved\`, skip stale low-signal candidates, and archive old reviewed items, but it never creates durable wiki pages automatically. Treat maintenance as a soft agent-side reminder; merge surfaced candidates when relevant or before ending the session, then mark them \`done\` or \`skipped\`.
20
20
  - Before creating new wiki pages, search existing pages and update the right one when possible. Reusable facts should not stay only in chunked \`outputs/questions/\`.
21
- - Store one-off work or decision records in \`llm-wiki/outputs/questions/YYYY-MM-DD/live-qa-001.md\` when needed. Merge reusable knowledge into \`wiki/architecture/\`, \`wiki/debugging/\`, \`wiki/decisions/\`, \`wiki/concepts/\`, or \`procedures/\` when approved or clearly important.
21
+ - Store one-off work or decision records in \`llm-wiki/outputs/questions/YYYY-MM-DD/live-qa-001.md\` when needed. Merge reusable knowledge into \`wiki/architecture/\`, \`wiki/debugging/\`, \`wiki/decisions/\`, \`wiki/concepts/\`, or \`procedures/\` when approved, hook-suggested, or clearly important.
22
22
  - Record verification commands, evidence files, and uncertainty. Mark inference explicitly and preserve contradictions in Open Questions or Contradictions.
23
23
  - Never store credentials, tokens, passwords, private keys, or raw \`.env\` contents. Store only redacted summaries when needed.
24
24
 
@@ -64,9 +64,10 @@ These rules replace older OMX/OMC/\`omx_wiki/\` rules for this project.
64
64
  - Important claims should include at least one of: \`source_ids\`, file paths, or verification commands.
65
65
  - When durable knowledge appears, search existing \`wiki/\` pages before creating a new page.
66
66
  - Do not promote simple answers, status checks, keyword-only replies, or one-off chat into live Q&A, \`wiki/queries\`, or maintenance.
67
- - Merge reusable knowledge into \`wiki/architecture/\`, \`wiki/debugging/\`, \`wiki/decisions/\`, \`wiki/concepts/\`, or \`procedures/\` based on importance and user consent flow.
68
- - Review \`outputs/maintenance/queue.md\` pending items only when related to the current request or when review is due. Merge into existing durable wiki pages, then mark items \`done\` or \`skipped\`.
69
- - Periodic maintenance is agent review, not automatic editing. Show only short reminders at \`SessionStart\`/\`InstructionsLoaded\`, and only show prompt-time reminders for wiki/maintenance-related user prompts.
67
+ - Hooks may flag work/decision turns as durable wiki candidates when they contain reusable architecture, debugging, policy, procedure, or decision signals. Treat those as review prompts, not automatic page creation.
68
+ - Merge reusable knowledge into \`wiki/architecture/\`, \`wiki/debugging/\`, \`wiki/decisions/\`, \`wiki/concepts/\`, or \`procedures/\` based on importance, hook suggestions, and user consent flow.
69
+ - Review \`outputs/maintenance/queue.md\` pending or approved items when surfaced by hook context, related to the current request, or review is due. Merge into existing durable wiki pages, then mark items \`done\` or \`skipped\`.
70
+ - Periodic maintenance is agent review, not automatic page creation. Session-start lifecycle may approve explicit/high-signal items, skip stale low-signal items, and archive old reviewed items. Show only short reminders at \`SessionStart\`/\`InstructionsLoaded\`, and compact prompt-time reminders when durable candidates or review thresholds need agent attention.
70
71
  - Keep \`wiki/memory.md\` short. Use links to current state and important documents instead of long explanations.
71
72
  - Preserve contradictions in \`Contradictions\` or \`Open Questions\`; do not overwrite them silently.
72
73
  - Do not store credentials, tokens, passwords, private keys, or raw \`.env\` contents in wiki.
@@ -105,7 +106,7 @@ superseded_by: []
105
106
  - query: use hook-injected context when useful, but answer the current request first. Manual commands are diagnostics, not required daily workflow.
106
107
  - lint: agent helper for user-requested or wiki maintenance work. Do not run it every turn.
107
108
  - consolidate: safely refresh generated blocks in \`memory.md\`/\`index.md\`. Do not overwrite handwritten sections or curated document bodies.
108
- - maintenance: \`outputs/maintenance/queue.md\` stores stop/start cleanup candidates. Review and update it only when it does not delay the current user request.
109
+ - maintenance: \`outputs/maintenance/queue.md\` stores stop/start cleanup candidates. Review surfaced durable candidates without delaying the current user request, then mark them \`done\` or \`skipped\`. Use \`llm-wiki maintenance --workspace <project> --apply-lifecycle --dry-run\` when queue hygiene needs inspection.
109
110
  `;
110
111
  }
111
112
 
@@ -198,7 +199,7 @@ export function procedure(name) {
198
199
  3. Create or update \`wiki/sources/<slug>.md\` only when a source summary is useful.
199
200
  4. Before creating a new page, look for related concept/entity/decision/architecture/debugging/context pages and update those first.
200
201
  5. Do not create duplicates. Merge facts into existing pages when they already exist.
201
- 6. Do not promote simple answers or one-off chat into durable wiki.
202
+ 6. Do not promote simple answers or one-off chat into durable wiki, but do review hook-suggested durable candidates for reusable facts.
202
203
  7. Important claims should record source references, confidence, memory type, importance, and verification status.
203
204
  8. When durable entry points change, update \`wiki/memory.md\` briefly or use \`llm-wiki consolidate\`.
204
205
  9. Add a short entry to \`wiki/log.md\` for meaningful wiki changes.
@@ -211,8 +212,8 @@ export function procedure(name) {
211
212
  4. Inspect raw sources only when exact evidence matters.
212
213
  5. Separate verified facts from inference.
213
214
  6. Use \`llm-wiki context "<query>"\` only when manual inspection is useful. Add \`--include-episodic\` only for historical query/context pages, and \`--include-archived\` only for archived/superseded pages.
214
- 7. Do not promote one-off answers, status checks, or keyword-only replies to durable wiki or live Q&A. Archive only work turns with tool evidence, changed-file evidence, verification, or structured decision/debugging conclusions.
215
- 8. Merge reusable facts into \`wiki/architecture/\`, \`wiki/debugging/\`, \`wiki/decisions/\`, \`wiki/concepts/\`, or \`procedures/\` based on importance and user consent flow.
215
+ 7. Do not promote one-off answers, status checks, or keyword-only replies to durable wiki or live Q&A. Archive only work turns with tool evidence, changed-file evidence, verification, structured decision/debugging conclusions, or hook-suggested durable candidates.
216
+ 8. Merge reusable facts into \`wiki/architecture/\`, \`wiki/debugging/\`, \`wiki/decisions/\`, \`wiki/concepts/\`, or \`procedures/\` based on importance, hook suggestions, and user consent flow.
216
217
  `,
217
218
  'lint.md': `# Lint Procedure
218
219
 
@@ -224,9 +225,10 @@ Periodic maintenance is an agent-side task, not a per-turn user command. When ne
224
225
 
225
226
  1. \`llm-wiki lint --workspace <project>\`
226
227
  2. \`llm-wiki maintenance --workspace <project>\`
227
- 3. Merge pending items into existing durable pages, then mark them \`done\` or \`skipped\`.
228
- 4. \`llm-wiki consolidate --workspace <project> --dry-run\`
229
- 5. Run \`llm-wiki consolidate --workspace <project>\` when the dry run is acceptable.
228
+ 3. \`llm-wiki maintenance --workspace <project> --apply-lifecycle --dry-run\`
229
+ 4. Merge pending, approved, or hook-suggested durable candidates into existing durable pages when appropriate, then mark them \`done\` or \`skipped\`.
230
+ 5. \`llm-wiki consolidate --workspace <project> --dry-run\`
231
+ 6. Run \`llm-wiki consolidate --workspace <project>\` when the dry run is acceptable.
230
232
 
231
233
  Apply automatic fixes only to clearly managed kit areas. Do not overwrite user-editable documents; surface cleanup needs in the next work context.
232
234
  `,