llm-wiki-kit 0.2.16 → 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
@@ -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
@@ -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
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, 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,7 +29,7 @@ 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
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.
@@ -37,7 +37,7 @@ Expected behavior:
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
@@ -75,6 +75,7 @@ Use Codex or Claude Code normally. Installed hooks:
75
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
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
 
@@ -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,6 +240,8 @@ 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;
@@ -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
  ```
@@ -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.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
 
@@ -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}`);
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';
@@ -110,6 +110,7 @@ async function handleAnswerFirstStop(projectRoot, eventName, payload, entry) {
110
110
  source: liveQaPath,
111
111
  eventName,
112
112
  reason: durableQueueReason(classification),
113
+ signalLevel: classification.signalLevel,
113
114
  }).catch(() => {});
114
115
  }
115
116
  await appendWikiLog(projectRoot, `captured ${eventName}; archive=${relative(projectRoot, liveQaPath)}; classification=${classification.kind}`);
@@ -155,6 +156,9 @@ export async function handleHook(provider, explicitEvent) {
155
156
  if (eventName === 'SessionStart' || eventName === 'InstructionsLoaded' || eventName === 'UserPromptSubmit') {
156
157
  await recoverStaleTurnStates(projectRoot).catch(() => []);
157
158
  }
159
+ if (eventName === 'SessionStart' || eventName === 'InstructionsLoaded') {
160
+ await applyMaintenanceLifecycle(projectRoot).catch(() => null);
161
+ }
158
162
 
159
163
  if (eventName === 'SessionStart' || eventName === 'InstructionsLoaded') {
160
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 });
@@ -469,6 +689,14 @@ export function formatMaintenanceResult(summary) {
469
689
  if ((summary.reviewReasons || []).length > 0) {
470
690
  lines.push(`- review reasons: ${summary.reviewReasons.join('; ')}`);
471
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
+ }
472
700
  if ((summary.approved || []).length > 0) {
473
701
  lines.push('', 'Approved:');
474
702
  for (const item of summary.approved.slice(0, 10)) {
package/src/templates.js CHANGED
@@ -16,7 +16,7 @@ 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 surfaced candidates when relevant or before ending the session, then 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
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.
@@ -66,8 +66,8 @@ These rules replace older OMX/OMC/\`omx_wiki/\` rules for this project.
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
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
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 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 editing. Show only short reminders at \`SessionStart\`/\`InstructionsLoaded\`, and compact prompt-time reminders when durable candidates or review thresholds need agent attention.
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.
71
71
  - Keep \`wiki/memory.md\` short. Use links to current state and important documents instead of long explanations.
72
72
  - Preserve contradictions in \`Contradictions\` or \`Open Questions\`; do not overwrite them silently.
73
73
  - Do not store credentials, tokens, passwords, private keys, or raw \`.env\` contents in wiki.
@@ -106,7 +106,7 @@ superseded_by: []
106
106
  - query: use hook-injected context when useful, but answer the current request first. Manual commands are diagnostics, not required daily workflow.
107
107
  - lint: agent helper for user-requested or wiki maintenance work. Do not run it every turn.
108
108
  - consolidate: safely refresh generated blocks in \`memory.md\`/\`index.md\`. Do not overwrite handwritten sections or curated document bodies.
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\`.
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.
110
110
  `;
111
111
  }
112
112
 
@@ -225,9 +225,10 @@ Periodic maintenance is an agent-side task, not a per-turn user command. When ne
225
225
 
226
226
  1. \`llm-wiki lint --workspace <project>\`
227
227
  2. \`llm-wiki maintenance --workspace <project>\`
228
- 3. Merge pending or hook-suggested durable candidates into existing durable pages when appropriate, then mark them \`done\` or \`skipped\`.
229
- 4. \`llm-wiki consolidate --workspace <project> --dry-run\`
230
- 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.
231
232
 
232
233
  Apply automatic fixes only to clearly managed kit areas. Do not overwrite user-editable documents; surface cleanup needs in the next work context.
233
234
  `,