Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion codex-marketplace/plugins/neatcontext/skills/save/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,11 @@ For creation:
}
```

`routingQuestions` holds 10 to 15 questions this context should answer, in the words a user would type rather than the words the profile uses. `routingEntities` holds the names that appear in this work and rarely elsewhere: services, repos, ticket ids, error strings, commands. Both are matched against and never shown, so prefer coverage over polish. On an update, omit both to leave the stored lists alone.
`routingQuestions` holds 10 to 15 questions this context should answer, in the words a user would type rather than the words the profile uses. `routingEntities` holds names that belong to the subject and appear rarely elsewhere: services, components, repositories, ticket ids and prefixes, error strings, product and system names. Both are matched against and never shown, so prefer coverage over polish.

Both lists travel with the context to anyone it is shared with, so write them as domain knowledge and nothing else: no absolute paths, no home directories, no usernames, no personal names, no email addresses, and nothing whose meaning depends on this machine or this person. If the work genuinely is about a particular environment, say so in the profile and keep these lists to terms a colleague would recognise.

On an update, omit both to leave the stored lists alone.

For an update, also include the exact `targetId` and `baseHash` printed by `save-target`. Every knowledge path must be a short relative `.md` path.

Expand Down
18 changes: 17 additions & 1 deletion codex-marketplace/plugins/neatcontext/src/core/context-store.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,22 @@ export async function createContext({ name, knowledgeFolder, profile, extensions
//
// Optional, unlike the description. A bundle written before this existed, or by
// a host that does not generate them, is not broken — it just matches on less.
// Terms that cannot mean the same thing on anyone else's machine, or that name
// a person rather than a subject.
//
// A context is domain knowledge, and it is meant to be handed to a teammate
// intact. A matching list is the easiest place for that to quietly stop being
// true: an absolute path or a home directory looks like a useful rare term
// while it is being written and is worthless — or worse, revealing — the moment
// the bundle leaves the machine.
//
// This drops them from the matching lists only. If a local path really is part
// of what the context is about, the profile and the knowledge folder are where
// it belongs and are untouched by this; being unable to *search* for a context
// by someone's home directory costs nothing worth having.
const NOT_PORTABLE =
/(^|\s)(~[\\/]|[a-z]:[\\/]|\\\\)|[\\/](home|users|root)[\\/]|\S+@\S+\.\S/i;

function normalizeRoutingList(value, limit) {
if (!Array.isArray(value)) {
return [];
Expand All @@ -325,7 +341,7 @@ function normalizeRoutingList(value, limit) {
if (typeof entry !== "string") continue;
const clean = entry.trim().replace(/\s+/g, " ").slice(0, MAX_ROUTING_TERM);
const key = clean.toLowerCase();
if (clean.length === 0 || seen.has(key)) continue;
if (clean.length === 0 || seen.has(key) || NOT_PORTABLE.test(clean)) continue;
seen.add(key);
kept.push(clean);
if (kept.length === limit) break;
Expand Down
12 changes: 10 additions & 2 deletions plugins/claude-code/neatcontext/commands/save.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,16 @@ lets someone find this context when they have forgotten it exists.
- `routingQuestions` — 10 to 15 questions this context should answer, written
the way the user would type them rather than the way the profile describes
them. Include the vague ones ("did we ever fix that timeout thing").
- `routingEntities` — names that appear in this work and rarely anywhere else:
services, repos, ticket ids, error strings, commands, hosts, people.
- `routingEntities` — names that belong to the subject and appear rarely
elsewhere: services, components, repositories, ticket ids and prefixes, error
strings, product and system names.

Both lists travel with the context to anyone it is shared with, so write them as
domain knowledge and nothing else. No absolute paths, no home directories, no
usernames, no personal names, no email addresses, and nothing whose meaning
depends on this machine or this person. If the work genuinely is about a
particular environment, say so in the profile — that is what the profile is for
— and keep these lists to the terms a colleague would recognise.

Nothing reads either list aloud, so prefer coverage over polish. On an update,
omit both fields to leave the stored lists untouched; supply them only when the
Expand Down
18 changes: 17 additions & 1 deletion plugins/claude-code/neatcontext/src/core/context-store.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,22 @@ export async function createContext({ name, knowledgeFolder, profile, extensions
//
// Optional, unlike the description. A bundle written before this existed, or by
// a host that does not generate them, is not broken — it just matches on less.
// Terms that cannot mean the same thing on anyone else's machine, or that name
// a person rather than a subject.
//
// A context is domain knowledge, and it is meant to be handed to a teammate
// intact. A matching list is the easiest place for that to quietly stop being
// true: an absolute path or a home directory looks like a useful rare term
// while it is being written and is worthless — or worse, revealing — the moment
// the bundle leaves the machine.
//
// This drops them from the matching lists only. If a local path really is part
// of what the context is about, the profile and the knowledge folder are where
// it belongs and are untouched by this; being unable to *search* for a context
// by someone's home directory costs nothing worth having.
const NOT_PORTABLE =
/(^|\s)(~[\\/]|[a-z]:[\\/]|\\\\)|[\\/](home|users|root)[\\/]|\S+@\S+\.\S/i;

function normalizeRoutingList(value, limit) {
if (!Array.isArray(value)) {
return [];
Expand All @@ -325,7 +341,7 @@ function normalizeRoutingList(value, limit) {
if (typeof entry !== "string") continue;
const clean = entry.trim().replace(/\s+/g, " ").slice(0, MAX_ROUTING_TERM);
const key = clean.toLowerCase();
if (clean.length === 0 || seen.has(key)) continue;
if (clean.length === 0 || seen.has(key) || NOT_PORTABLE.test(clean)) continue;
seen.add(key);
kept.push(clean);
if (kept.length === limit) break;
Expand Down
12 changes: 10 additions & 2 deletions plugins/copilot/neatcontext/commands/save.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,16 @@ lets someone find this context when they have forgotten it exists.
- `routingQuestions` — 10 to 15 questions this context should answer, written
the way the user would type them rather than the way the profile describes
them. Include the vague ones ("did we ever fix that timeout thing").
- `routingEntities` — names that appear in this work and rarely anywhere else:
services, repos, ticket ids, error strings, commands, hosts, people.
- `routingEntities` — names that belong to the subject and appear rarely
elsewhere: services, components, repositories, ticket ids and prefixes, error
strings, product and system names.

Both lists travel with the context to anyone it is shared with, so write them as
domain knowledge and nothing else. No absolute paths, no home directories, no
usernames, no personal names, no email addresses, and nothing whose meaning
depends on this machine or this person. If the work genuinely is about a
particular environment, say so in the profile — that is what the profile is for
— and keep these lists to the terms a colleague would recognise.

Nothing reads either list aloud, so prefer coverage over polish. On an update,
omit both fields to leave the stored lists untouched; supply them only when the
Expand Down
18 changes: 17 additions & 1 deletion plugins/copilot/neatcontext/src/core/context-store.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,22 @@ export async function createContext({ name, knowledgeFolder, profile, extensions
//
// Optional, unlike the description. A bundle written before this existed, or by
// a host that does not generate them, is not broken — it just matches on less.
// Terms that cannot mean the same thing on anyone else's machine, or that name
// a person rather than a subject.
//
// A context is domain knowledge, and it is meant to be handed to a teammate
// intact. A matching list is the easiest place for that to quietly stop being
// true: an absolute path or a home directory looks like a useful rare term
// while it is being written and is worthless — or worse, revealing — the moment
// the bundle leaves the machine.
//
// This drops them from the matching lists only. If a local path really is part
// of what the context is about, the profile and the knowledge folder are where
// it belongs and are untouched by this; being unable to *search* for a context
// by someone's home directory costs nothing worth having.
const NOT_PORTABLE =
/(^|\s)(~[\\/]|[a-z]:[\\/]|\\\\)|[\\/](home|users|root)[\\/]|\S+@\S+\.\S/i;

function normalizeRoutingList(value, limit) {
if (!Array.isArray(value)) {
return [];
Expand All @@ -325,7 +341,7 @@ function normalizeRoutingList(value, limit) {
if (typeof entry !== "string") continue;
const clean = entry.trim().replace(/\s+/g, " ").slice(0, MAX_ROUTING_TERM);
const key = clean.toLowerCase();
if (clean.length === 0 || seen.has(key)) continue;
if (clean.length === 0 || seen.has(key) || NOT_PORTABLE.test(clean)) continue;
seen.add(key);
kept.push(clean);
if (kept.length === limit) break;
Expand Down
6 changes: 5 additions & 1 deletion plugins/kimi-code/neatcontext/skills/save/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,11 @@ For creation:
}
```

`routingQuestions` holds 10 to 15 questions this context should answer, in the words a user would type rather than the words the profile uses. `routingEntities` holds the names that appear in this work and rarely elsewhere: services, repos, ticket ids, error strings, commands. Both are matched against and never shown, so prefer coverage over polish. On an update, omit both to leave the stored lists alone.
`routingQuestions` holds 10 to 15 questions this context should answer, in the words a user would type rather than the words the profile uses. `routingEntities` holds names that belong to the subject and appear rarely elsewhere: services, components, repositories, ticket ids and prefixes, error strings, product and system names. Both are matched against and never shown, so prefer coverage over polish.

Both lists travel with the context to anyone it is shared with, so write them as domain knowledge and nothing else: no absolute paths, no home directories, no usernames, no personal names, no email addresses, and nothing whose meaning depends on this machine or this person. If the work genuinely is about a particular environment, say so in the profile and keep these lists to terms a colleague would recognise.

On an update, omit both to leave the stored lists alone.

For an update, also include the exact `targetId` and `baseHash` printed by `save-target`. Every knowledge path must be a short relative `.md` path.

Expand Down
18 changes: 17 additions & 1 deletion plugins/kimi-code/neatcontext/src/core/context-store.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,22 @@ export async function createContext({ name, knowledgeFolder, profile, extensions
//
// Optional, unlike the description. A bundle written before this existed, or by
// a host that does not generate them, is not broken — it just matches on less.
// Terms that cannot mean the same thing on anyone else's machine, or that name
// a person rather than a subject.
//
// A context is domain knowledge, and it is meant to be handed to a teammate
// intact. A matching list is the easiest place for that to quietly stop being
// true: an absolute path or a home directory looks like a useful rare term
// while it is being written and is worthless — or worse, revealing — the moment
// the bundle leaves the machine.
//
// This drops them from the matching lists only. If a local path really is part
// of what the context is about, the profile and the knowledge folder are where
// it belongs and are untouched by this; being unable to *search* for a context
// by someone's home directory costs nothing worth having.
const NOT_PORTABLE =
/(^|\s)(~[\\/]|[a-z]:[\\/]|\\\\)|[\\/](home|users|root)[\\/]|\S+@\S+\.\S/i;

function normalizeRoutingList(value, limit) {
if (!Array.isArray(value)) {
return [];
Expand All @@ -325,7 +341,7 @@ function normalizeRoutingList(value, limit) {
if (typeof entry !== "string") continue;
const clean = entry.trim().replace(/\s+/g, " ").slice(0, MAX_ROUTING_TERM);
const key = clean.toLowerCase();
if (clean.length === 0 || seen.has(key)) continue;
if (clean.length === 0 || seen.has(key) || NOT_PORTABLE.test(clean)) continue;
seen.add(key);
kept.push(clean);
if (kept.length === limit) break;
Expand Down
8 changes: 5 additions & 3 deletions plugins/pi/neatcontext/extensions/neatcontext.js
Original file line number Diff line number Diff line change
Expand Up @@ -285,9 +285,11 @@ export default function (pi) {
type: "array",
items: { type: "string" },
description:
"Names that appear in this work and rarely elsewhere: services, repos, ticket " +
"ids, error strings, commands, hosts. Matched against, never shown. Omit on an " +
"update to leave the stored list alone."
"Names belonging to the subject that appear rarely elsewhere: services, " +
"components, repositories, ticket ids and prefixes, error strings, product and " +
"system names. This travels with the context when it is shared, so no absolute " +
"paths, home directories, usernames, personal names or email addresses. Matched " +
"against, never shown. Omit on an update to leave the stored list alone."
},
knowledge: {
type: "array",
Expand Down
12 changes: 10 additions & 2 deletions plugins/pi/neatcontext/skills/save/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,16 @@ lets someone find this context when they have forgotten it exists.
- `routingQuestions` — 10 to 15 questions this context should answer, written
the way the user would type them rather than the way the profile describes
them. Include the vague ones ("did we ever fix that timeout thing").
- `routingEntities` — names that appear in this work and rarely anywhere else:
services, repos, ticket ids, error strings, commands, hosts, people.
- `routingEntities` — names that belong to the subject and appear rarely
elsewhere: services, components, repositories, ticket ids and prefixes, error
strings, product and system names.

Both lists travel with the context to anyone it is shared with, so write them as
domain knowledge and nothing else. No absolute paths, no home directories, no
usernames, no personal names, no email addresses, and nothing whose meaning
depends on this machine or this person. If the work genuinely is about a
particular environment, say so in the profile — that is what the profile is for
— and keep these lists to the terms a colleague would recognise.

Nothing reads either list aloud, so prefer coverage over polish. On an update,
omit both fields to leave the stored lists untouched; supply them only when the
Expand Down
18 changes: 17 additions & 1 deletion plugins/pi/neatcontext/src/core/context-store.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,22 @@ export async function createContext({ name, knowledgeFolder, profile, extensions
//
// Optional, unlike the description. A bundle written before this existed, or by
// a host that does not generate them, is not broken — it just matches on less.
// Terms that cannot mean the same thing on anyone else's machine, or that name
// a person rather than a subject.
//
// A context is domain knowledge, and it is meant to be handed to a teammate
// intact. A matching list is the easiest place for that to quietly stop being
// true: an absolute path or a home directory looks like a useful rare term
// while it is being written and is worthless — or worse, revealing — the moment
// the bundle leaves the machine.
//
// This drops them from the matching lists only. If a local path really is part
// of what the context is about, the profile and the knowledge folder are where
// it belongs and are untouched by this; being unable to *search* for a context
// by someone's home directory costs nothing worth having.
const NOT_PORTABLE =
/(^|\s)(~[\\/]|[a-z]:[\\/]|\\\\)|[\\/](home|users|root)[\\/]|\S+@\S+\.\S/i;

function normalizeRoutingList(value, limit) {
if (!Array.isArray(value)) {
return [];
Expand All @@ -325,7 +341,7 @@ function normalizeRoutingList(value, limit) {
if (typeof entry !== "string") continue;
const clean = entry.trim().replace(/\s+/g, " ").slice(0, MAX_ROUTING_TERM);
const key = clean.toLowerCase();
if (clean.length === 0 || seen.has(key)) continue;
if (clean.length === 0 || seen.has(key) || NOT_PORTABLE.test(clean)) continue;
seen.add(key);
kept.push(clean);
if (kept.length === limit) break;
Expand Down
18 changes: 17 additions & 1 deletion shared/core/context-store.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,22 @@ export async function createContext({ name, knowledgeFolder, profile, extensions
//
// Optional, unlike the description. A bundle written before this existed, or by
// a host that does not generate them, is not broken — it just matches on less.
// Terms that cannot mean the same thing on anyone else's machine, or that name
// a person rather than a subject.
//
// A context is domain knowledge, and it is meant to be handed to a teammate
// intact. A matching list is the easiest place for that to quietly stop being
// true: an absolute path or a home directory looks like a useful rare term
// while it is being written and is worthless — or worse, revealing — the moment
// the bundle leaves the machine.
//
// This drops them from the matching lists only. If a local path really is part
// of what the context is about, the profile and the knowledge folder are where
// it belongs and are untouched by this; being unable to *search* for a context
// by someone's home directory costs nothing worth having.
const NOT_PORTABLE =
/(^|\s)(~[\\/]|[a-z]:[\\/]|\\\\)|[\\/](home|users|root)[\\/]|\S+@\S+\.\S/i;

function normalizeRoutingList(value, limit) {
if (!Array.isArray(value)) {
return [];
Expand All @@ -325,7 +341,7 @@ function normalizeRoutingList(value, limit) {
if (typeof entry !== "string") continue;
const clean = entry.trim().replace(/\s+/g, " ").slice(0, MAX_ROUTING_TERM);
const key = clean.toLowerCase();
if (clean.length === 0 || seen.has(key)) continue;
if (clean.length === 0 || seen.has(key) || NOT_PORTABLE.test(clean)) continue;
seen.add(key);
kept.push(clean);
if (kept.length === limit) break;
Expand Down
18 changes: 18 additions & 0 deletions tests/routing-expansion-docs.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,24 @@ describe("every host asks for the matching material", () => {
assert.match(text, /never shown|matched against and never shown|Matched against, never shown/i);
});

it(`${host} says the lists travel and must stay portable`, async () => {
// The lists go into the bundle, and the bundle is meant to be handed to a
// teammate intact. A host that asks for hostnames or people gets a
// context that is worse to share than the work it came from.
const text = await readFile(path.join(root, file), "utf8");
assert.match(text, /travels? with the context|travel with the context/i);
assert.match(text, /absolute paths/i);
assert.match(text, /usernames|personal names/i);
});

it(`${host} never asks for hostnames or people`, async () => {
// The wording this replaced did, which is how machine- and person-
// specific terms would have reached a shared bundle.
const text = await readFile(path.join(root, file), "utf8");
assert.ok(!/error strings, commands, hosts/i.test(text), `${file} still asks for hosts`);
assert.ok(!/commands, hosts, people/i.test(text), `${file} still asks for people`);
});

it(`${host} says omitting them leaves stored lists alone`, async () => {
// The rule that stops a host from wiping what another host wrote.
const text = await readFile(path.join(root, file), "utf8");
Expand Down
Loading