Skip to content

feat(copilot): add Nowledge Mem plugin for Copilot CLI#201

Open
HamsteRider-m wants to merge 2 commits intonowledge-co:mainfrom
HamsteRider-m:copilot/copilot-cli-plugin
Open

feat(copilot): add Nowledge Mem plugin for Copilot CLI#201
HamsteRider-m wants to merge 2 commits intonowledge-co:mainfrom
HamsteRider-m:copilot/copilot-cli-plugin

Conversation

@HamsteRider-m
Copy link
Copy Markdown

@HamsteRider-m HamsteRider-m commented Apr 16, 2026

Summary

  • add a dedicated nowledge-mem-copilot-cli-plugin package with Copilot-native hooks, commands, skills, installer, docs, and tests
  • capture Copilot CLI sessions incrementally into Nowledge Mem with secret filtering, lock protection, incomplete-turn detection, and auto-distill guardrails
  • register the new integration in integrations.json so it can ship through the Nowledge community marketplace

Why this exists

GitHub Copilot CLI can load Claude Code-style plugins, but using the Claude Code integration directly is brittle in practice because some lifecycle assumptions, command surfaces, and config paths are Claude-specific.

This PR therefore treats the Copilot plugin as a real adaptation, not a straight copy:

  • behavior and product intent were inherited from the Claude Code integration
  • Copilot-facing packaging, hook wiring, installer flow, and transcript capture were rewritten for Copilot CLI
  • the result preserves the Nowledge Mem workflow on Copilot without relying on Claude-only runtime assumptions

Implementation approach

The recorded build session shows an orchestrator-worker approach rather than a role-play pipeline:

  • parallel research across the existing Claude Code, Hermes, Codex, Droid, OpenCode, and registry patterns
  • a rubber-duck critique pass before implementation
  • phased implementation across registry, hooks/skills/commands, Python capture runtime, docs, and tests
  • a final independent external review pass requested from GPT-5.4

The captured planning artifacts also show three design corrections made before/during implementation:

  1. switch thread identity from per-turn IDs to stable per-session IDs: copilot-{session_id}
  2. make the installer idempotent instead of assuming a one-shot setup flow
  3. keep Python as the primary runtime and add platform-aware locking behavior instead of depending on a Unix-only shell path

What the reviewer should look at first

If you want to review from the source of the design rather than only from the final diff, start here:

  • nowledge-mem-copilot-cli-plugin/scripts/copilot-stop-save.py — core Copilot transcript capture and incremental thread save logic
  • nowledge-mem-copilot-cli-plugin/hooks/hooks.json — lifecycle wiring for SessionStart, UserPromptSubmit, and async Stop
  • nowledge-mem-copilot-cli-plugin/README.md and AGENTS.md — user-facing and agent-facing behavior model
  • integrations.json — registry entry for the new copilot-cli integration

Session evidence and validation notes

The Copilot session artifacts I reviewed show:

  • the main implementation session running on claude-opus-4.6
  • parallel exploration work recorded with claude-haiku-4.5
  • rubber-duck / external-review activity recorded against gpt-5.4

I did not find reliable evidence for a specific reasoning-effort tier such as high, so I am intentionally not claiming that here.

The session artifacts clearly preserve the planning and review flow. They are less consistent on which validation steps were fully completed versus merely planned, so I would treat the recorded validation narrative as implementation context rather than as the authoritative release checklist.

Test plan

  • local install flow exercised via scripts/install-hooks.sh
  • plugin assets and registry entry are wired for Copilot CLI
  • please run your normal maintainer validation pass on the current branch before merge

🤖 Generated with Claude Code

Full-featured plugin with auto-capture, working memory, search, and distill.

- Plugin manifest (.claude-plugin/plugin.json) v0.1.0
- 4 lifecycle hooks (SessionStart×2, UserPromptSubmit, Stop)
- 4 skills (read-working-memory, search-memory, distill-memory, save-thread)
- 4 commands (/save, /search, /sum, /status)
- Session capture script (copilot-stop-save.py) with:
  - Incremental append via nmem t import + nmem t append fallback
  - Secret filtering (6 patterns) and sensitive content detection
  - Auto-distill with cooldown, content hash dedup, min thresholds
  - Cross-platform locking (fcntl/msvcrt)
  - Incomplete turn detection (background tasks, ask_user, questions)
- Idempotent installer (install-hooks.sh)
- Registry entry in integrations.json (plugin-capture transport)
- Full test suite (14 tests covering all capture script logic)
- Documentation: README, CHANGELOG, RELEASING, AGENTS.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 16, 2026

Warning

Rate limit exceeded

@HamsteRider-m has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 27 minutes and 3 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 27 minutes and 3 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 612e4832-40d4-4cbb-8203-71dfb400269a

📥 Commits

Reviewing files that changed from the base of the PR and between eee5555 and 894f17b.

📒 Files selected for processing (7)
  • nowledge-mem-copilot-cli-plugin/AGENTS.md
  • nowledge-mem-copilot-cli-plugin/CHANGELOG.md
  • nowledge-mem-copilot-cli-plugin/README.md
  • nowledge-mem-copilot-cli-plugin/RELEASING.md
  • nowledge-mem-copilot-cli-plugin/skills/distill-memory/SKILL.md
  • nowledge-mem-copilot-cli-plugin/skills/save-thread/SKILL.md
  • nowledge-mem-copilot-cli-plugin/skills/search-memory/SKILL.md
📝 Walkthrough

Walkthrough

This PR introduces a new Copilot CLI plugin integration for Nowledge Mem, adding plugin registration, lifecycle hook handlers, CLI command documentation, Python-based session transcript capture with secret redaction, and comprehensive test coverage to enable persistent knowledge management within Copilot CLI sessions.

Changes

Cohort / File(s) Summary
Integration Registration
integrations.json
Added new copilot-cli integration entry declaring capabilities (workingMemory, search, distill, autoRecall, autoCapture, status), autonomy policies, slash commands (/save, /search, /sum, /status), and marketplace installation instructions.
Plugin Metadata
nowledge-mem-copilot-cli-plugin/.claude-plugin/plugin.json
New plugin manifest defining name, version 0.1.0, author contact, repository, license (MIT), and semantic keywords for the Copilot CLI integration.
Core Documentation
nowledge-mem-copilot-cli-plugin/{AGENTS,CHANGELOG,README,RELEASING}.md
Plugin behavior specification (hook-based working memory loading and session capture), initial release notes, installation/setup instructions (including WSL bridging), and step-by-step release process with validation checklist.
Command Documentation
nowledge-mem-copilot-cli-plugin/commands/{save,search,sum,status}.md
CLI command guides covering on-demand session saving, memory search with filtering, distillation workflow with importance scoring, and Nowledge Mem connectivity status verification.
Hook Configuration
nowledge-mem-copilot-cli-plugin/hooks/hooks.json
Lifecycle hook definitions for SessionStart (load working memory with fallbacks), UserPromptSubmit (proactive memory search injection), and Stop (async session capture via Python script).
Hook and Installation Scripts
nowledge-mem-copilot-cli-plugin/scripts/{copilot-stop-save.py,copilot-stop-save.sh,install-hooks.sh}
Python transcript processor with secret redaction, state locking, and auto-distillation; Bash launcher; and idempotent installer that deploys hooks to ~/.copilot/nowledge-mem-hooks/.
Skills Documentation
nowledge-mem-copilot-cli-plugin/skills/*/{read-working-memory,search-memory,save-thread,distill-memory}/SKILL.md
Agent behavior guides for autonomous memory operations: briefing retrieval, semantic search with progressive inspection, explicit session summaries, and insight extraction with quality standards.
Tests
nowledge-mem-copilot-cli-plugin/tests/test_copilot_plugin.py
Comprehensive pytest suite covering transcript event parsing, multi-pattern secret redaction (GitHub tokens, API keys, private keys, JWTs), message extraction, title generation, content hashing, state persistence, and platform-specific command construction.

Sequence Diagram

sequenceDiagram
    participant COP as Copilot CLI
    participant HOOK as Hook System
    participant PYSC as Python Capture Script
    participant NMEM as nmem CLI
    participant FS as Filesystem

    COP->>HOOK: SessionStart (startup/resume)
    HOOK->>NMEM: nmem --json wm read
    NMEM->>HOOK: Return working memory JSON
    HOOK->>COP: Inject working memory context

    COP->>HOOK: UserPromptSubmit (per-turn)
    HOOK->>NMEM: nmem --json m search "query"
    NMEM->>HOOK: Return memory results
    HOOK->>COP: Inject search suggestion

    COP->>HOOK: Stop (end_turn)
    HOOK->>PYSC: exec(copilot-stop-save.py) via stdin
    PYSC->>FS: Read/write state lock at ~/.copilot/nowledge-mem-hooks/state/
    PYSC->>PYSC: Parse transcript, redact secrets, extract turn
    PYSC->>NMEM: nmem t import (create thread)
    alt Thread exists
        PYSC->>NMEM: nmem t append (add turn)
    end
    NMEM->>FS: Store thread in Nowledge Mem
    PYSC->>NMEM: nmem t triage + distill (auto-extract insights)
    NMEM->>FS: Store distilled memories
    PYSC->>FS: Log action to hook-log.jsonl
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

Poem

🐰 A CLI plugin hops with glee,
Capturing sessions for memory!
With hooks that save each clever turn,
And secrets scrubbed, the lessons learn—
No knowledge lost, just knowledge grown! 🌱

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.48% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title accurately and concisely summarizes the main addition: a new Nowledge Mem plugin for GitHub Copilot CLI. It is specific, clear, and directly related to the primary change in the changeset.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: eee5555b54

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

return [json.loads(line) for line in fh]


def find_index(events: list[dict], event_id: str | None) -> int | None:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep capture script compatible with advertised Python 3

This script uses 3.10-only type syntax (str | None / -> int | None), but the installer only checks for python3 and does not require 3.10+, so users on Python 3.8/3.9 will hit a SyntaxError before the Stop hook can run. In those environments, session capture silently never works (the hook command ends with || true). Either enforce a minimum Python version during install or replace these annotations with older-compatible typing syntax.

Useful? React with 👍 / 👎.

Comment on lines +574 to +577
triage = run_json(
build_nmem_command(
nmem_bin, "--json", "t", "triage", thread_id
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Persist state even when triage/distill fails

A failure in nmem t triage (for example, older nmem versions without this subcommand or transient backend errors) raises out of main() before last_saved_turn_end_id is updated, because state is saved only later. After that, each Stop hook reprocesses the same turn and keeps trying to import/append it again, causing repeated duplicate appends and noisy errors. Triage/distill should be best-effort and must not block state advancement once thread import/append succeeded.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (10)
nowledge-mem-copilot-cli-plugin/skills/save-thread/SKILL.md (1)

10-12: Include /save in explicit trigger examples.

Please add /save to the explicit “when to save” trigger list so the skill aligns with command-driven usage.

As per coding guidelines: Use slash commands (/save, /search <query>, /sum, /status) to manage Nowledge Mem integration.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nowledge-mem-copilot-cli-plugin/skills/save-thread/SKILL.md` around lines 10
- 12, Update the explicit trigger examples in SKILL.md's "Only when user
explicitly says:" list to include the slash command `/save` alongside the
existing phrases ("Save this session", "Checkpoint this", "Record conversation")
so the skill supports command-driven usage; edit the trigger list in SKILL.md to
add `/save` and ensure consistency with other command examples (`/search
<query>`, `/sum`, `/status`) mentioned in the guidelines.
nowledge-mem-copilot-cli-plugin/commands/status.md (1)

9-13: Prefer /status as the command shown to users.

Please make /status the top-level invocation in this page, and keep nmem status as implementation/troubleshooting detail.

As per coding guidelines: Use slash commands (/save, /search <query>, /sum, /status) to manage Nowledge Mem integration.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nowledge-mem-copilot-cli-plugin/commands/status.md` around lines 9 - 13,
Update the example in status.md to present "/status" as the primary invocation
and move "nmem status" into a secondary implementation/troubleshooting note;
specifically replace the top-level code block showing "nmem status" with
"/status" and add a small following paragraph or code block that notes the
equivalent CLI command "nmem status" for debugging or local runs so both usages
appear but "/status" is shown as the canonical user-facing command.
nowledge-mem-copilot-cli-plugin/commands/search.md (1)

6-14: Add /search <query> as the primary command example.

The current section is technically correct, but this command page should lead with the slash command and then optionally show the underlying nmem --json ... call.

As per coding guidelines: Use slash commands (/save, /search <query>, /sum, /status) to manage Nowledge Mem integration.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nowledge-mem-copilot-cli-plugin/commands/search.md` around lines 6 - 14, The
documentation should lead with the slash command example; update the Search
Memory section to present "/search <query>" as the primary command example
(e.g., show a code block with "/search <query>") and then optionally include the
underlying CLI invocation "nmem --json m search \"$ARGUMENTS\"" afterwards;
ensure you keep the existing heading "Search Memory" and the explanatory
sentence, and follow the guideline to use slash commands (`/save`, `/search
<query>`, `/sum`, `/status`) as the primary examples.
nowledge-mem-copilot-cli-plugin/commands/sum.md (1)

7-30: Make /sum the primary invocation in this command page.

This page currently teaches direct nmem usage first; for consistency with plugin UX, show /sum as the primary action and keep raw CLI as implementation detail/reference.

As per coding guidelines: Use slash commands (/save, /search <query>, /sum, /status) to manage Nowledge Mem integration.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nowledge-mem-copilot-cli-plugin/commands/sum.md` around lines 7 - 30, The
page should present the slash command /sum as the primary invocation and move
the raw nmem CLI example (nmem m add ...) to a secondary "Implementation/CLI
reference" section; update the introduction and the top-level example to show
"/sum" (and related slash commands /save, /search <query>, /status) as the
recommended workflow, then retain the existing nmem CLI snippet only as an
optional reference and label it accordingly; modify headings and order in sum.md
and adjust any examples or usage steps that currently teach direct nmem usage
first so they now demonstrate slash-command usage first.
nowledge-mem-copilot-cli-plugin/commands/save.md (1)

9-17: Show /save first in the Usage section.

Since this is the /save command doc, lead with slash-command usage and keep nmem t create ... as the underlying execution detail.

As per coding guidelines: Use slash commands (/save, /search <query>, /sum, /status) to manage Nowledge Mem integration.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nowledge-mem-copilot-cli-plugin/commands/save.md` around lines 9 - 17,
Reorder the Usage section to lead with the slash command `/save` (showing the
exact slash-command usage first) and then present the `nmem t create "..."
--title "..." -s copilot-cli` example as the underlying execution detail; update
the text to state that `/save` triggers an immediate save and that `nmem t
create` is the equivalent lower-level invocation, and ensure the slash-command
conforms to the established style used elsewhere (`/save`, `/search <query>`,
`/sum`, `/status`) so readers see the slash command first and the `nmem t
create` form as supplemental.
nowledge-mem-copilot-cli-plugin/tests/test_copilot_plugin.py (3)

324-345: Test isolation: mutating module-level STATE_DIR can cause flaky tests.

Directly assigning copilot_stop_save.STATE_DIR = Path(tmpdir) mutates global state that persists across tests. If test ordering changes or tests run in parallel, this can cause failures. Also, the unpacked path and path2 variables are unused (per static analysis).

♻️ Proposed fix using `patch` and underscore prefix
 class TestStateManagement:
     def test_load_state_new_session(self):
         with tempfile.TemporaryDirectory() as tmpdir:
-            copilot_stop_save.STATE_DIR = Path(tmpdir)
-            path, state = copilot_stop_save.load_state("new-session")
+            with patch.object(copilot_stop_save, "STATE_DIR", Path(tmpdir)):
+                _path, state = copilot_stop_save.load_state("new-session")
             assert state["active_start_event_id"] is None
             assert state["last_saved_turn_end_id"] is None
             assert state["last_distill_ts"] == 0
             assert state["last_content_hash"] is None

     def test_save_and_load_state(self):
         with tempfile.TemporaryDirectory() as tmpdir:
-            copilot_stop_save.STATE_DIR = Path(tmpdir)
-            path, state = copilot_stop_save.load_state("test-session")
-            state["last_saved_turn_end_id"] = "e4"
-            state["last_distill_ts"] = 1000
-            state["last_content_hash"] = "abc123"
-            copilot_stop_save.save_state(path, state)
-
-            path2, state2 = copilot_stop_save.load_state("test-session")
-            assert state2["last_saved_turn_end_id"] == "e4"
-            assert state2["last_distill_ts"] == 1000
-            assert state2["last_content_hash"] == "abc123"
+            with patch.object(copilot_stop_save, "STATE_DIR", Path(tmpdir)):
+                path, state = copilot_stop_save.load_state("test-session")
+                state["last_saved_turn_end_id"] = "e4"
+                state["last_distill_ts"] = 1000
+                state["last_content_hash"] = "abc123"
+                copilot_stop_save.save_state(path, state)
+
+                _path2, state2 = copilot_stop_save.load_state("test-session")
+                assert state2["last_saved_turn_end_id"] == "e4"
+                assert state2["last_distill_ts"] == 1000
+                assert state2["last_content_hash"] == "abc123"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nowledge-mem-copilot-cli-plugin/tests/test_copilot_plugin.py` around lines
324 - 345, The tests mutate the module-level copilot_stop_save.STATE_DIR
directly which can leak between tests; update both test_load_state_new_session
and test_save_and_load_state to patch STATE_DIR only for the test scope (use
unittest.mock.patch or pytest monkeypatch to set copilot_stop_save.STATE_DIR =
Path(tmpdir) inside the context) and avoid assigning unused variables by
renaming path and path2 to _path/_path2 or not capturing them when calling
load_state; keep calls to load_state and save_state unchanged.

383-392: Test leaves temporary file behind on assertion failure.

If an assertion fails before os.unlink(f.name), the temp file remains. Using a context manager with delete_on_close=False (Python 3.12+) or a try/finally block ensures cleanup:

♻️ Proposed fix for reliable cleanup
     def test_load_events_from_jsonl(self):
         events = basic_transcript()
-        with tempfile.NamedTemporaryFile("w", suffix=".jsonl", delete=False) as f:
-            for event in events:
-                f.write(json.dumps(event) + "\n")
-            f.flush()
-            loaded = copilot_stop_save.load_events(f.name)
-            assert len(loaded) == len(events)
-            assert loaded[0]["type"] == "user.message"
-            os.unlink(f.name)
+        with tempfile.TemporaryDirectory() as tmpdir:
+            fpath = Path(tmpdir) / "transcript.jsonl"
+            with fpath.open("w") as f:
+                for event in events:
+                    f.write(json.dumps(event) + "\n")
+            loaded = copilot_stop_save.load_events(str(fpath))
+            assert len(loaded) == len(events)
+            assert loaded[0]["type"] == "user.message"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nowledge-mem-copilot-cli-plugin/tests/test_copilot_plugin.py` around lines
383 - 392, The test test_load_events_from_jsonl leaves the temp file behind if
an assertion fails; wrap the body that writes, calls
copilot_stop_save.load_events and asserts in a try/finally so os.unlink(f.name)
is always called (or use a context manager that ensures deletion), locating the
tempfile usage in test_load_events_from_jsonl and the call to
copilot_stop_save.load_events to add the try/finally cleanup around those
operations.

151-174: Secret redaction tests use weak assertions.

The assertions like assert "ghp_" not in result or "[REDACTED]" in result pass even if the token is partially present. A stricter check ensures the secret is fully redacted:

♻️ Proposed stricter assertions
     def test_redacts_github_token(self):
         text = "Use token ghp_1234567890abcdefghijklmn"
         result = copilot_stop_save.redact(text)
-        assert "ghp_" not in result or "[REDACTED]" in result
+        assert "ghp_1234567890abcdefghijklmn" not in result
+        assert "[REDACTED]" in result

     def test_redacts_github_pat(self):
         text = "Token: github_pat_1234567890abcdefghijklmn"
         result = copilot_stop_save.redact(text)
-        assert "github_pat_" not in result or "[REDACTED]" in result
+        assert "github_pat_1234567890abcdefghijklmn" not in result
+        assert "[REDACTED]" in result

     def test_redacts_openai_key(self):
         text = "API key is sk-1234567890abcdefgh"
         result = copilot_stop_save.redact(text)
-        assert "sk-" not in result or "[REDACTED]" in result
+        assert "sk-1234567890abcdefgh" not in result
+        assert "[REDACTED]" in result
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nowledge-mem-copilot-cli-plugin/tests/test_copilot_plugin.py` around lines
151 - 174, The tests (test_redacts_github_token, test_redacts_github_pat,
test_redacts_openai_key, test_redacts_bearer_token) use weak OR assertions that
allow partial secrets to remain; update each to assert the secret prefix is
absent AND that "[REDACTED]" appears (e.g. replace `assert "ghp_" not in result
or "[REDACTED]" in result` with `assert "ghp_" not in result and "[REDACTED]" in
result`), and do the same for "github_pat_", "sk-" and the bearer case when
calling copilot_stop_save.redact to ensure full redaction while keeping
test_preserves_normal_text unchanged.
nowledge-mem-copilot-cli-plugin/README.md (1)

32-37: WSL shim may break on arguments containing spaces or special characters.

The quoting approach q="$q \"$a\"" doesn't handle arguments with embedded quotes, backslashes, or other special characters. Consider using printf '%q ' for proper shell escaping:

♻️ Proposed fix for robust argument handling
 mkdir -p ~/.local/bin && cat > ~/.local/bin/nmem << 'SHIMEOF'
 #!/bin/bash
-q=""; for a in "$@"; do q="$q \"$a\""; done
-cmd.exe /s /c "\"nmem.cmd\"$q"
+args=()
+for a in "$@"; do args+=("$(printf '%q' "$a")"); done
+cmd.exe /s /c "nmem.cmd ${args[*]}"
 SHIMEOF
 chmod +x ~/.local/bin/nmem
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nowledge-mem-copilot-cli-plugin/README.md` around lines 32 - 37, The WSL shim
in the nmem script builds q with q="$q \"$a\"" which fails on embedded
quotes/backslashes; update the argument-quoting loop in the nmem shim to use a
robust shell-escaping approach (e.g., build q with printf '%q' for each "$a" or
otherwise use an array and proper quoting) so arguments containing
spaces/special chars are escaped correctly; look for the q variable and the for
a in "$@" loop in the nmem shim and replace the naive concatenation with printf
'%q' "$a" (or an equivalent safe-escaping strategy) before invoking cmd.exe.
nowledge-mem-copilot-cli-plugin/scripts/copilot-stop-save.py (1)

314-321: File locking on Windows locks only 1 byte of an empty file.

msvcrt.locking(lock_fh.fileno(), msvcrt.LK_LOCK, 1) locks 1 byte, but the file is empty (just opened with "w"). While this works as a mutex (any concurrent lock attempt will block), the lock is never released explicitly—relying on file handle close. Also, if the file grows beyond 1 byte from another process writing, the lock scope is ambiguous.

Consider writing a sentinel byte before locking, or document this is intentional mutex-style locking:

♻️ Proposed improvement
     with lock_path.open("w", encoding="utf-8") as lock_fh:
+        lock_fh.write("L")  # Sentinel byte for msvcrt locking
+        lock_fh.flush()
         if fcntl:
             fcntl.flock(lock_fh, fcntl.LOCK_EX)
         else:
             import msvcrt
             msvcrt.locking(lock_fh.fileno(), msvcrt.LK_LOCK, 1)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nowledge-mem-copilot-cli-plugin/scripts/copilot-stop-save.py` around lines
314 - 321, The Windows branch currently calls msvcrt.locking(lock_fh.fileno(),
msvcrt.LK_LOCK, 1) on an empty file which leads to ambiguous locking; change the
Windows path (around lock_path, lock_fh, session_id, and the fcntl/ mscrt
branch) to first write a sentinel byte (e.g., b"\0") to the opened file, flush()
and seek(0) so the file actually contains that byte, then call
msvcrt.locking(..., msvcrt.LK_LOCK, 1) to lock that byte; keep the existing
behavior for fcntl, and rely on closing lock_fh to release the lock as before.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@nowledge-mem-copilot-cli-plugin/CHANGELOG.md`:
- Line 8: The release heading "## [0.1.0] - 2026-07-14" uses a future date;
update that heading in CHANGELOG.md (the "## [0.1.0] - 2026-07-14" line) to
either the actual release date (e.g., "2026-04-16") or replace the date with
"Unreleased" until the real release date is known, ensuring the header stays
exactly "## [0.1.0] - <date or Unreleased>".

In `@nowledge-mem-copilot-cli-plugin/RELEASING.md`:
- Around line 5-7: Reorder the three release steps so the `integrations.json`
update happens before the `.claude-plugin/plugin.json` update: first bump the
`copilot-cli` version in integrations.json (the canonical source), then update
the `version` field in `.claude-plugin/plugin.json`, and finally add the new
section to `CHANGELOG.md`; ensure the RELEASING.md steps reflect that exact
sequence and phrasing.

In `@nowledge-mem-copilot-cli-plugin/scripts/copilot-stop-save.py`:
- Around line 240-254: The load_state function (and any code that builds
lock_path or other filesystem paths from session_id) currently embeds raw
session_id into STATE_DIR / f"{session_id}.json" which allows path traversal;
add a sanitization/normalization step (e.g., a sanitize_session_id(session_id)
helper called from main() and before any use in load_state or when creating
lock_path) that either (a) validates allowed characters and rejects or
canonicalizes input (strip path separators, dots, and directory components), or
(b) replaces the session_id with a safe deterministic token (e.g., hex of
sha256(session_id) or a UUID derived from it) and use that token for filenames;
update load_state, any lock_path construction, and callers in main() to use the
sanitized token instead of the raw session_id.
- Around line 125-134: The redact function currently discards group 1 prefixes
by replacing the entire match when pattern.groups == 1; update the logic in
redact (which loops over SECRET_PATTERNS and checks pattern.groups) so that for
pattern.groups == 1 you preserve the first capture and append “[REDACTED]”
(i.e., perform the same substitution used for 2+ groups such as using the
"\1[REDACTED]" replacement), keep 0-group behavior unchanged, and ensure
SECRET_PATTERNS and pattern.groups are referenced as-is to locate the change.

In `@nowledge-mem-copilot-cli-plugin/skills/read-working-memory/SKILL.md`:
- Around line 34-36: Replace the plain-shell example command "nmem wm read" with
the JSON-mode command "nmem --json wm read" in SKILL.md (update the code block
that contains the command), and scan the same MD file for any other occurrences
of "nmem wm read" to change them to the "--json" variant so recall examples
follow the structured parsing convention.

In `@nowledge-mem-copilot-cli-plugin/skills/save-thread/SKILL.md`:
- Around line 33-37: Update the fenced code block that contains the "✓ Thread
saved" message so it declares a language identifier (e.g., change the opening
``` to ```text) to satisfy markdown linting; locate the fenced block that wraps
"✓ Thread saved / Summary: {summary} / Thread ID: {thread_id from nmem output}"
in SKILL.md and add the language token immediately after the opening backticks.

---

Nitpick comments:
In `@nowledge-mem-copilot-cli-plugin/commands/save.md`:
- Around line 9-17: Reorder the Usage section to lead with the slash command
`/save` (showing the exact slash-command usage first) and then present the `nmem
t create "..." --title "..." -s copilot-cli` example as the underlying execution
detail; update the text to state that `/save` triggers an immediate save and
that `nmem t create` is the equivalent lower-level invocation, and ensure the
slash-command conforms to the established style used elsewhere (`/save`,
`/search <query>`, `/sum`, `/status`) so readers see the slash command first and
the `nmem t create` form as supplemental.

In `@nowledge-mem-copilot-cli-plugin/commands/search.md`:
- Around line 6-14: The documentation should lead with the slash command
example; update the Search Memory section to present "/search <query>" as the
primary command example (e.g., show a code block with "/search <query>") and
then optionally include the underlying CLI invocation "nmem --json m search
\"$ARGUMENTS\"" afterwards; ensure you keep the existing heading "Search Memory"
and the explanatory sentence, and follow the guideline to use slash commands
(`/save`, `/search <query>`, `/sum`, `/status`) as the primary examples.

In `@nowledge-mem-copilot-cli-plugin/commands/status.md`:
- Around line 9-13: Update the example in status.md to present "/status" as the
primary invocation and move "nmem status" into a secondary
implementation/troubleshooting note; specifically replace the top-level code
block showing "nmem status" with "/status" and add a small following paragraph
or code block that notes the equivalent CLI command "nmem status" for debugging
or local runs so both usages appear but "/status" is shown as the canonical
user-facing command.

In `@nowledge-mem-copilot-cli-plugin/commands/sum.md`:
- Around line 7-30: The page should present the slash command /sum as the
primary invocation and move the raw nmem CLI example (nmem m add ...) to a
secondary "Implementation/CLI reference" section; update the introduction and
the top-level example to show "/sum" (and related slash commands /save, /search
<query>, /status) as the recommended workflow, then retain the existing nmem CLI
snippet only as an optional reference and label it accordingly; modify headings
and order in sum.md and adjust any examples or usage steps that currently teach
direct nmem usage first so they now demonstrate slash-command usage first.

In `@nowledge-mem-copilot-cli-plugin/README.md`:
- Around line 32-37: The WSL shim in the nmem script builds q with q="$q \"$a\""
which fails on embedded quotes/backslashes; update the argument-quoting loop in
the nmem shim to use a robust shell-escaping approach (e.g., build q with printf
'%q' for each "$a" or otherwise use an array and proper quoting) so arguments
containing spaces/special chars are escaped correctly; look for the q variable
and the for a in "$@" loop in the nmem shim and replace the naive concatenation
with printf '%q' "$a" (or an equivalent safe-escaping strategy) before invoking
cmd.exe.

In `@nowledge-mem-copilot-cli-plugin/scripts/copilot-stop-save.py`:
- Around line 314-321: The Windows branch currently calls
msvcrt.locking(lock_fh.fileno(), msvcrt.LK_LOCK, 1) on an empty file which leads
to ambiguous locking; change the Windows path (around lock_path, lock_fh,
session_id, and the fcntl/ mscrt branch) to first write a sentinel byte (e.g.,
b"\0") to the opened file, flush() and seek(0) so the file actually contains
that byte, then call msvcrt.locking(..., msvcrt.LK_LOCK, 1) to lock that byte;
keep the existing behavior for fcntl, and rely on closing lock_fh to release the
lock as before.

In `@nowledge-mem-copilot-cli-plugin/skills/save-thread/SKILL.md`:
- Around line 10-12: Update the explicit trigger examples in SKILL.md's "Only
when user explicitly says:" list to include the slash command `/save` alongside
the existing phrases ("Save this session", "Checkpoint this", "Record
conversation") so the skill supports command-driven usage; edit the trigger list
in SKILL.md to add `/save` and ensure consistency with other command examples
(`/search <query>`, `/sum`, `/status`) mentioned in the guidelines.

In `@nowledge-mem-copilot-cli-plugin/tests/test_copilot_plugin.py`:
- Around line 324-345: The tests mutate the module-level
copilot_stop_save.STATE_DIR directly which can leak between tests; update both
test_load_state_new_session and test_save_and_load_state to patch STATE_DIR only
for the test scope (use unittest.mock.patch or pytest monkeypatch to set
copilot_stop_save.STATE_DIR = Path(tmpdir) inside the context) and avoid
assigning unused variables by renaming path and path2 to _path/_path2 or not
capturing them when calling load_state; keep calls to load_state and save_state
unchanged.
- Around line 383-392: The test test_load_events_from_jsonl leaves the temp file
behind if an assertion fails; wrap the body that writes, calls
copilot_stop_save.load_events and asserts in a try/finally so os.unlink(f.name)
is always called (or use a context manager that ensures deletion), locating the
tempfile usage in test_load_events_from_jsonl and the call to
copilot_stop_save.load_events to add the try/finally cleanup around those
operations.
- Around line 151-174: The tests (test_redacts_github_token,
test_redacts_github_pat, test_redacts_openai_key, test_redacts_bearer_token) use
weak OR assertions that allow partial secrets to remain; update each to assert
the secret prefix is absent AND that "[REDACTED]" appears (e.g. replace `assert
"ghp_" not in result or "[REDACTED]" in result` with `assert "ghp_" not in
result and "[REDACTED]" in result`), and do the same for "github_pat_", "sk-"
and the bearer case when calling copilot_stop_save.redact to ensure full
redaction while keeping test_preserves_normal_text unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: afb203e7-f806-40ad-87b3-00520c2d7682

📥 Commits

Reviewing files that changed from the base of the PR and between 0052bf1 and eee5555.

⛔ Files ignored due to path filters (1)
  • nowledge-mem-copilot-cli-plugin/icon.png is excluded by !**/*.png
📒 Files selected for processing (19)
  • integrations.json
  • nowledge-mem-copilot-cli-plugin/.claude-plugin/plugin.json
  • nowledge-mem-copilot-cli-plugin/AGENTS.md
  • nowledge-mem-copilot-cli-plugin/CHANGELOG.md
  • nowledge-mem-copilot-cli-plugin/README.md
  • nowledge-mem-copilot-cli-plugin/RELEASING.md
  • nowledge-mem-copilot-cli-plugin/commands/save.md
  • nowledge-mem-copilot-cli-plugin/commands/search.md
  • nowledge-mem-copilot-cli-plugin/commands/status.md
  • nowledge-mem-copilot-cli-plugin/commands/sum.md
  • nowledge-mem-copilot-cli-plugin/hooks/hooks.json
  • nowledge-mem-copilot-cli-plugin/scripts/copilot-stop-save.py
  • nowledge-mem-copilot-cli-plugin/scripts/copilot-stop-save.sh
  • nowledge-mem-copilot-cli-plugin/scripts/install-hooks.sh
  • nowledge-mem-copilot-cli-plugin/skills/distill-memory/SKILL.md
  • nowledge-mem-copilot-cli-plugin/skills/read-working-memory/SKILL.md
  • nowledge-mem-copilot-cli-plugin/skills/save-thread/SKILL.md
  • nowledge-mem-copilot-cli-plugin/skills/search-memory/SKILL.md
  • nowledge-mem-copilot-cli-plugin/tests/test_copilot_plugin.py

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.1.0] - 2026-07-14
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Release date is set in the future.

Line 8 uses 2026-07-14, which is after today (April 16, 2026). Please switch to the actual release date or use an Unreleased section until release day.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nowledge-mem-copilot-cli-plugin/CHANGELOG.md` at line 8, The release heading
"## [0.1.0] - 2026-07-14" uses a future date; update that heading in
CHANGELOG.md (the "## [0.1.0] - 2026-07-14" line) to either the actual release
date (e.g., "2026-04-16") or replace the date with "Unreleased" until the real
release date is known, ensuring the header stays exactly "## [0.1.0] - <date or
Unreleased>".

Comment on lines +5 to +7
1. Update `version` in `.claude-plugin/plugin.json`
2. Update `version` for the `copilot-cli` entry in `integrations.json` (repo root)
3. Add a new section to `CHANGELOG.md`
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Reorder release steps to update integrations.json first

Line 5-Line 7 currently instruct updating .claude-plugin/plugin.json before integrations.json. Swap these so integrations.json is updated first to match the registry workflow contract.

As per coding guidelines: integrations.json is the single source of truth and should be updated first when adding/modifying an integration.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nowledge-mem-copilot-cli-plugin/RELEASING.md` around lines 5 - 7, Reorder the
three release steps so the `integrations.json` update happens before the
`.claude-plugin/plugin.json` update: first bump the `copilot-cli` version in
integrations.json (the canonical source), then update the `version` field in
`.claude-plugin/plugin.json`, and finally add the new section to `CHANGELOG.md`;
ensure the RELEASING.md steps reflect that exact sequence and phrasing.

Comment on lines +125 to +134
def redact(text: str) -> str:
redacted = text or ""
for pattern in SECRET_PATTERNS:
if pattern.groups == 0:
redacted = pattern.sub("[REDACTED]", redacted)
elif pattern.groups == 1:
redacted = pattern.sub("[REDACTED]", redacted)
else:
redacted = pattern.sub(r"\1[REDACTED]", redacted)
return redacted
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Redaction logic discards intended prefixes for single-group patterns.

When pattern.groups == 1, the current code replaces the entire match with [REDACTED], losing the prefix captured in group 1. Patterns like the Bearer token pattern (line 56) capture "Bearer " in group 1 specifically to preserve it. The logic should be:

  • 0 groups → replace whole match
  • 1 group → preserve group 1 as prefix
  • 2+ groups → already handled with \1[REDACTED]
🐛 Proposed fix
 def redact(text: str) -> str:
     redacted = text or ""
     for pattern in SECRET_PATTERNS:
         if pattern.groups == 0:
             redacted = pattern.sub("[REDACTED]", redacted)
         elif pattern.groups == 1:
-            redacted = pattern.sub("[REDACTED]", redacted)
+            redacted = pattern.sub(r"\1[REDACTED]", redacted)
         else:
             redacted = pattern.sub(r"\1[REDACTED]", redacted)
     return redacted

Or simplify since groups >= 1 now have the same behavior:

 def redact(text: str) -> str:
     redacted = text or ""
     for pattern in SECRET_PATTERNS:
         if pattern.groups == 0:
             redacted = pattern.sub("[REDACTED]", redacted)
         else:
             redacted = pattern.sub(r"\1[REDACTED]", redacted)
     return redacted
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def redact(text: str) -> str:
redacted = text or ""
for pattern in SECRET_PATTERNS:
if pattern.groups == 0:
redacted = pattern.sub("[REDACTED]", redacted)
elif pattern.groups == 1:
redacted = pattern.sub("[REDACTED]", redacted)
else:
redacted = pattern.sub(r"\1[REDACTED]", redacted)
return redacted
def redact(text: str) -> str:
redacted = text or ""
for pattern in SECRET_PATTERNS:
if pattern.groups == 0:
redacted = pattern.sub("[REDACTED]", redacted)
elif pattern.groups == 1:
redacted = pattern.sub(r"\1[REDACTED]", redacted)
else:
redacted = pattern.sub(r"\1[REDACTED]", redacted)
return redacted
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nowledge-mem-copilot-cli-plugin/scripts/copilot-stop-save.py` around lines
125 - 134, The redact function currently discards group 1 prefixes by replacing
the entire match when pattern.groups == 1; update the logic in redact (which
loops over SECRET_PATTERNS and checks pattern.groups) so that for pattern.groups
== 1 you preserve the first capture and append “[REDACTED]” (i.e., perform the
same substitution used for 2+ groups such as using the "\1[REDACTED]"
replacement), keep 0-group behavior unchanged, and ensure SECRET_PATTERNS and
pattern.groups are referenced as-is to locate the change.

Comment on lines +240 to +254
def load_state(session_id: str) -> tuple[Path, dict]:
STATE_DIR.mkdir(parents=True, exist_ok=True)
path = STATE_DIR / f"{session_id}.json"
if not path.exists():
return path, {
"active_start_event_id": None,
"last_saved_turn_end_id": None,
"last_distill_ts": 0,
"last_content_hash": None,
}
with path.open(encoding="utf-8") as fh:
state = json.load(fh)
state.setdefault("last_distill_ts", 0)
state.setdefault("last_content_hash", None)
return path, state
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Session ID used directly in filesystem path without sanitization.

session_id comes from external input (stdin payload) and is used directly to construct file paths (lines 242, 314). A malicious session_id like ../../../etc/passwd could cause path traversal. Sanitize or validate:

🛡️ Proposed fix to sanitize session_id
+import re as _re
+
+_SAFE_SESSION_ID_RE = _re.compile(r"^[A-Za-z0-9_-]{1,128}$")
+
+def _sanitize_session_id(session_id: str) -> str:
+    """Ensure session_id is safe for use in filenames."""
+    if not session_id or not _SAFE_SESSION_ID_RE.match(session_id):
+        # Fall back to hash if invalid
+        return hashlib.sha256(session_id.encode()).hexdigest()[:32]
+    return session_id
+
 def load_state(session_id: str) -> tuple[Path, dict]:
+    session_id = _sanitize_session_id(session_id)
     STATE_DIR.mkdir(parents=True, exist_ok=True)
     path = STATE_DIR / f"{session_id}.json"

And apply similar sanitization at line 314 for lock_path, or sanitize once in main() before any use.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nowledge-mem-copilot-cli-plugin/scripts/copilot-stop-save.py` around lines
240 - 254, The load_state function (and any code that builds lock_path or other
filesystem paths from session_id) currently embeds raw session_id into STATE_DIR
/ f"{session_id}.json" which allows path traversal; add a
sanitization/normalization step (e.g., a sanitize_session_id(session_id) helper
called from main() and before any use in load_state or when creating lock_path)
that either (a) validates allowed characters and rejects or canonicalizes input
(strip path separators, dots, and directory components), or (b) replaces the
session_id with a safe deterministic token (e.g., hex of sha256(session_id) or a
UUID derived from it) and use that token for filenames; update load_state, any
lock_path construction, and callers in main() to use the sanitized token instead
of the raw session_id.

Comment on lines +34 to +36
```bash
nmem wm read
```
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use JSON mode for Working Memory recall command

Line 35 should use nmem --json wm read to keep recall flows consistent with structured parsing expectations.

Suggested doc fix
- nmem wm read
+ nmem --json wm read

As per coding guidelines: for nowledge-mem-copilot-cli-plugin/**/*.{md,sh,bash,py,yml,yaml}, use the nmem CLI with --json flag to search and recall persistent knowledge.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```bash
nmem wm read
```
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nowledge-mem-copilot-cli-plugin/skills/read-working-memory/SKILL.md` around
lines 34 - 36, Replace the plain-shell example command "nmem wm read" with the
JSON-mode command "nmem --json wm read" in SKILL.md (update the code block that
contains the command), and scan the same MD file for any other occurrences of
"nmem wm read" to change them to the "--json" variant so recall examples follow
the structured parsing convention.

Comment on lines +33 to +37
```
✓ Thread saved
Summary: {summary}
Thread ID: {thread_id from nmem output}
```
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add a language identifier to the fenced response block.

The fenced block at Line 33 should declare a language (for example, text) to satisfy markdown lint expectations.

Suggested patch
-```
+```text
 ✓ Thread saved
 Summary: {summary}
 Thread ID: {thread_id from nmem output}
</details>

<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion

🧰 Tools
🪛 markdownlint-cli2 (0.22.0)

[warning] 33-33: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nowledge-mem-copilot-cli-plugin/skills/save-thread/SKILL.md` around lines 33
- 37, Update the fenced code block that contains the "✓ Thread saved" message so
it declares a language identifier (e.g., change the opening ``` to ```text) to
satisfy markdown linting; locate the fenced block that wraps "✓ Thread saved /
Summary: {summary} / Thread ID: {thread_id from nmem output}" in SKILL.md and
add the language token immediately after the opening backticks.

Clarify that Copilot CLI currently ships bundled command docs without exposing them as interactive slash commands, and update the install path examples accordingly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant