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