@damiangilgonzalez/harness 1.3.18 → 1.3.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -169,11 +169,6 @@ Un `git pull` no escribe nada en tu máquina; `rsc doctor` te dice qué ya no cu
169
169
  **Skills propias.** Las que escribe tu equipo viven en el repo y funcionan al clonar.
170
170
  rsc nunca las instala, actualiza ni sobrescribe: su versión es el commit.
171
171
 
172
- ![Memoria entre sesiones: cada sesión retoma donde lo dejó la anterior](docs/img/readme/readme-13-memoria.png)
173
-
174
- Al cerrar, guarda rama, ficheros y commits; al abrir, te dice dónde lo dejaste.
175
- Nunca guarda prompts, respuestas ni secretos, y no sale de tu máquina. `rsc memory off` lo apaga.
176
-
177
172
  ![Una rama, una copia de trabajo](docs/img/readme/readme-18-worktrees.png)
178
173
 
179
174
  Cada rama en su worktree, cortada siempre desde `origin`.
@@ -195,6 +190,63 @@ npx @damiangilgonzalez/harness@latest repair --dry-run
195
190
 
196
191
  ---
197
192
 
193
+ ## La memoria
194
+
195
+ ![Los cuatro niveles de memoria y qué pasa en cada mensaje](docs/img/readme/readme-19-memoria-niveles.png)
196
+
197
+ El agente empieza cada conversación en blanco. Lo que recuerda está en cuatro sitios, y cada uno
198
+ responde a una pregunta distinta:
199
+
200
+ | Nivel | Qué guarda | Dónde | Quién lo escribe |
201
+ |---|---|---|---|
202
+ | **1. Wiki del proyecto** | Lo que el proyecto *sabe*: decisiones, stack, hechos del negocio | `docs/wiki/` (en git) | El agente cuando dices «recuerda que…» |
203
+ | **2. Diario de sesión** | *Dónde lo dejaste*: rama, ficheros tocados, commits | `.rsc/memory/` (solo tu máquina, 30 días) | Los hooks, solos |
204
+ | **3. Lecciones aprobadas** | Reglas cortas para cada sesión de este proyecto | `.rsc/memory/lessons/` (solo tu máquina) | Tú, con `rsc memory learn … --approve` |
205
+ | **4. Memoria de Claude Code** | *Cómo te gusta trabajar a ti* | `~/.claude/projects/` (fuera del repo) | Claude, cuando le hablas de ti |
206
+
207
+ ![Quién escribe cada nivel, cómo se activa y cuándo se lee](docs/img/readme/readme-20-memoria-quien-cuando.png)
208
+
209
+ **Nada guarda tus conversaciones.** Solo se recuerda lo que alguien escribe en uno de los cuatro
210
+ sitios:
211
+
212
+ | Nivel | ¿Cómo se escribe? | ¿Qué haces tú? | ¿Cuándo lo lee el agente? |
213
+ |---|---|---|---|
214
+ | **1. Wiki** | El agente, cuando se lo pides, o una skill que documenta su trabajo (`harness`, `continuous-learning`…) | Decir «recuerda que…» | Cuando la tarea lo pide: `CLAUDE.md` le dice que existe `docs/wiki/index.md` |
215
+ | **2. Diario** | Automático: los hooks, en cada evento | Nada | Automático, al abrir sesión |
216
+ | **3. Lecciones** | Solo con un comando, y solo con `--approve` | `rsc memory learn --text "…" --approve` | Automático, al abrir sesión |
217
+ | **4. Memoria de Claude** | Claude Code, cuando le cuentas algo de ti | Decir «recuerda que prefiero…» | Automático, al abrir sesión (`MEMORY.md`) |
218
+
219
+ «Recuerda que…» sobre el proyecto va al nivel 1, y el agente te dice la ruta de la página. Si no
220
+ te da una ruta dentro de `docs/wiki/`, no lo ha guardado en el proyecto.
221
+
222
+ **Qué pasa en cada mensaje** (no al abrir rama ni sesión: en cada mensaje):
223
+
224
+ 1. Envías un mensaje.
225
+ 2. Antes de que el agente lo lea, un hook trae a **tu rama actual** lo nuevo de la rama
226
+ `rsc/knowledge` del remoto (solo `docs/wiki/`, `docs/attachments/` y `01-TOOLS/`) y te avisa
227
+ con una línea `📥 Traídos…`.
228
+ 3. El agente responde con la wiki al día.
229
+ 4. Al terminar, otro hook sube a `rsc/knowledge` lo que cambió en esas carpetas.
230
+
231
+ `rsc/knowledge` es una rama de intercambio, no una PR: lo que escribe un compañero te llega sin
232
+ revisión, y llega a `main` dentro de vuestras PRs normales. Nunca trae código, nunca pisa un
233
+ fichero que estás editando y nunca comitea en una `main` cerrada. Trabajando solo, sirve para que
234
+ lo que anotas en una rama aparezca en las demás. Lo que se trae es lo que se descargó en el
235
+ mensaje anterior; si la última descarga tiene más de 3 minutos, descarga antes (3 s como mucho).
236
+
237
+ ![Memoria entre sesiones: cada sesión retoma donde lo dejó la anterior](docs/img/readme/readme-13-memoria.png)
238
+
239
+ El diario (nivel 2) nunca guarda prompts, respuestas ni secretos, y no sale de tu máquina.
240
+
241
+ ```bash
242
+ npx @damiangilgonzalez/harness memory resume # lo que recibirá el agente al abrir sesión
243
+ npx @damiangilgonzalez/harness knowledge-sync status # ¿se sincroniza la wiki? y si no, por qué
244
+ npx @damiangilgonzalez/harness memory off # apaga el diario y las lecciones
245
+ npx @damiangilgonzalez/harness knowledge-sync off # apaga la sincronización (todo el proyecto)
246
+ ```
247
+
248
+ Guía completa: [la memoria del harness](docs/como-funciona/memoria.md).
249
+
198
250
  ## Catálogo
199
251
 
200
252
  ![294 skills por dominio](docs/img/readme/readme-15-catalogo.png)
package/manifest.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.3.18",
2
+ "version": "1.3.20",
3
3
  "counts": {
4
4
  "skills": 294,
5
5
  "agents": 34,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@damiangilgonzalez/harness",
3
- "version": "1.3.18",
3
+ "version": "1.3.20",
4
4
  "description": "A guided harness builder that turns your goal into a minimal, verified AI-agent setup using strict best practices.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -21,6 +21,7 @@ import {
21
21
  import {
22
22
  wireMemory, unwireMemory, memoryManagedPaths, memoryModeFor, memoryEnabledForProject, memoryArtifactsPresent,
23
23
  } from '../targets/memory.js';
24
+ import { wireKnowledge, unwireKnowledge, knowledgeManagedPaths, knowledgeArtifactsPresent } from '../targets/knowledge-wiring.js';
24
25
 
25
26
  const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
26
27
  const CLI_VERSION = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8')).version;
@@ -94,6 +95,7 @@ export function managedPathsForInstall({ skillIds, agentIds = [], target, home,
94
95
  const out = [paths.stateFile, versionFile(cwd), baseVersionsFile(cwd)];
95
96
  if (policy?.context7 === false) out.push(join(cwd, '.rsc', '.no-context7'));
96
97
  if (policy?.memory !== false) out.push(...memoryManagedPaths(target, cwd));
98
+ out.push(...knowledgeManagedPaths(target, cwd));
97
99
  if (targetHasAgents(target)) {
98
100
  const state = readState(paths.stateFile);
99
101
  const explicit = [...new Set([...(state.explicitAgents || readManifest(cwd)?.agents || []), ...agentIds])];
@@ -273,6 +275,10 @@ export async function applyInstall({ skillIds = [], agentIds = [], target, home,
273
275
  ? { mode: 'disabled', reason: 'onboarding-policy', paths: unwireMemory(target, cwd) }
274
276
  : wireMemory(target, cwd);
275
277
  state.memory = { mode: memoryResult.mode, reason: memoryResult.reason, paths: memoryResult.paths };
278
+ // Knowledge sync is its own decision (`.no-knowledge-sync`, checked at runtime) and never part of
279
+ // the memory, so the memory's policy does not decide it: wired wherever the assistant can run it.
280
+ const knowledgeResult = wireKnowledge(target, cwd);
281
+ state.knowledge = { mode: knowledgeResult.mode, reason: knowledgeResult.reason || null, paths: knowledgeResult.paths };
276
282
  const context7OptOut = join(cwd, '.rsc', '.no-context7');
277
283
  if (policy?.context7 === false) {
278
284
  mkdirSync(dirname(context7OptOut), { recursive: true });
@@ -587,6 +593,7 @@ export function removeTargetInstall({ target, home, cwd = process.cwd() }) {
587
593
  }
588
594
  unwireHook(target, paths);
589
595
  unwireMemory(target, cwd);
596
+ unwireKnowledge(target, cwd);
590
597
  rmSync(paths.stateFile, { force: true });
591
598
  const gitignore = join(cwd, '.gitignore');
592
599
  if (existsSync(gitignore)) {
@@ -731,8 +738,10 @@ export async function purge({ home, cwd = process.cwd(), withDocs = false, dryRu
731
738
  if (!dryRun) {
732
739
  removed.push(...unwireHook(target, paths));
733
740
  removed.push(...unwireMemory(target, cwd));
741
+ removed.push(...unwireKnowledge(target, cwd));
734
742
  } else {
735
743
  removed.push(...memoryArtifactsPresent(target, cwd));
744
+ removed.push(...knowledgeArtifactsPresent(target, cwd));
736
745
  }
737
746
  // A lost state file also loses proof of ownership. Leave same-named user files
738
747
  // behind rather than guessing from a catalog id and deleting their work.
package/scripts/rsc.js CHANGED
@@ -41,7 +41,7 @@ if (['--version', '-v', 'version'].includes(rawArgv[0])) {
41
41
  const GLOBAL_VALUE_FLAGS = new Set([
42
42
  '--target', '--technical-level', '--accompaniment', '--project-kind', '--goal', '--goal-base64', '--software-scope', '--accept-plan',
43
43
  ]);
44
- const COMMANDS = new Set(['onboard', 'reassess', 'add', 'install', 'consult', 'catalog', 'audit', 'list', 'doctor', 'memory', 'sync', 'backups', 'restore', 'upgrade', 'registry', 'worktrees', 'capabilities', 'review', 'sello', 'repair', 'uninstall', 'purge']);
44
+ const COMMANDS = new Set(['onboard', 'reassess', 'add', 'install', 'consult', 'catalog', 'audit', 'list', 'doctor', 'memory', 'knowledge-sync', 'sync', 'backups', 'restore', 'upgrade', 'registry', 'worktrees', 'capabilities', 'review', 'sello', 'repair', 'uninstall', 'purge']);
45
45
  function positionalTokens(input) {
46
46
  const out = [];
47
47
  for (let i = 0; i < input.length; i++) {
@@ -756,6 +756,32 @@ async function main() {
756
756
  printContextBudget(report.contextBudget);
757
757
  return void say(JSON.stringify({ ...report, contextBudget: undefined }, null, 2));
758
758
  }
759
+ case 'knowledge-sync': {
760
+ // On by default; the switch is a PROJECT decision (`.no-knowledge-sync`, recorded in `.rsc.json`
761
+ // optOuts by the sync below), because one person off means the team stops seeing their work.
762
+ const sub = argv[1] || 'status';
763
+ const root = process.cwd();
764
+ const { mkdirSync, writeFileSync, rmSync } = await import('node:fs');
765
+ const { join } = await import('node:path');
766
+ const marker = join(root, '.rsc', '.no-knowledge-sync');
767
+ if (sub === 'on' || sub === 'off') {
768
+ mkdirSync(join(root, '.rsc'), { recursive: true });
769
+ if (sub === 'off') writeFileSync(marker, '');
770
+ else rmSync(marker, { force: true });
771
+ for (const t of targets) await syncInstalled({ target: t, cwd: root });
772
+ say(sub === 'off'
773
+ ? 'rsc knowledge-sync off: 01-TOOLS/ and docs/ no longer sync on their own in this project. Commit .rsc.json so the team gets the same decision.'
774
+ : 'rsc knowledge-sync on: 01-TOOLS/ and docs/ go up when a turn ends and come down before each message. Commit .rsc.json so the team gets the same decision.');
775
+ return;
776
+ }
777
+ if (sub === 'status') {
778
+ const { knowledgeStatus } = await import('../targets/knowledge-sync.mjs');
779
+ return void say(JSON.stringify(knowledgeStatus(root), null, 2));
780
+ }
781
+ say('Use: npx @damiangilgonzalez/harness@latest knowledge-sync on|off|status');
782
+ process.exitCode = 2;
783
+ return;
784
+ }
759
785
  case 'memory': {
760
786
  const sub = argv[1] || 'status';
761
787
  const root = process.cwd();
@@ -71,13 +71,12 @@ name it and offer it. This runs mid-conversation, not only at project start.
71
71
  1. `npx @damiangilgonzalez/harness catalog --available` lists every not-installed skill as
72
72
  `id available short description`.
73
73
  2. Pick the single best fit **by meaning**, the way you would match a request to a teammate's
74
- expertise — "mandar emails de bienvenida" → an email/outreach skill, though not one keyword
75
- overlaps. If nothing genuinely fits, say so
76
- and move on: a tangential suggestion is worse than none.
74
+ expertise, even when no keyword overlaps. If nothing genuinely fits, say so and move on: a
75
+ tangential suggestion is worse than none.
77
76
  3. Ask once, plainly: "Para esto instalaría `<id>`, que aún no tienes. ¿La instalo? (sí/no)".
78
77
  4. On yes, run `npx @damiangilgonzalez/harness add <id>`, then continue the original task.
79
78
 
80
- Installing changes the user's environment, so it is always their call. One suggestion at a time,
79
+ One suggestion at a time,
81
80
  and never to interrupt a flow with a nice-to-have. Never recommend something already installed
82
81
  (`npx @damiangilgonzalez/harness list`).
83
82
 
@@ -95,9 +94,8 @@ Not covered → one line, skill or agent. Rules: `skill-scout`.
95
94
 
96
95
  ## 3. A harness that is broken fixes itself
97
96
 
98
- You are injected into every session, so you are the only thing that can notice a broken
99
- harness before its owner does — and its owner usually cannot, because the symptoms name
100
- nothing they recognise. When any of these is true, act on it **once** in the session:
97
+ Its owner rarely notices: the symptoms name nothing they know. When any of these
98
+ is true, act on it **once** in the session:
101
99
 
102
100
  - `.rsc.json` exists and what it declares is not what is installed;
103
101
  - the same hook seems to run several times;
@@ -117,7 +115,7 @@ a cause. Then:
117
115
  teammate added, disarming a gate — **ask first**. A `git pull` never rewrites someone's
118
116
  machine.
119
117
 
120
- Offer once per session. Never mention any of it when the harness is healthy.
118
+ Offer once per session; say nothing when the harness is healthy.
121
119
 
122
120
  ## 4. First contact
123
121
 
@@ -130,6 +128,12 @@ Before handling the first request of a session, check the workspace:
130
128
  `.rsc/.no-harness`, confirm in one line, and never auto-start `init` in this repo again.
131
129
  - Once the profile exists, this gate is inert. Never re-onboard.
132
130
 
131
+ ## 5. «Recuerda esto»
132
+
133
+ A project or business fact goes to its topic page in `docs/wiki/` (link a new page from
134
+ `wiki/index.md`); say the path. Your own memory sits outside the project, unseen by person and
135
+ team: keep there only how this person likes you to work (or all, without `docs/`).
136
+
133
137
  ## Explain without assuming
134
138
 
135
139
  Define a term at first use; never give a command, flag or path without saying what it does.
@@ -76,3 +76,9 @@ capability:
76
76
  - "Names that one missing automation/connector skill"
77
77
  - "Asks one short confirmation before installing and does not auto-install"
78
78
  - "On confirmation, runs `npx @damiangilgonzalez/harness add <id>` for that one missing skill, then resumes the automation task"
79
+
80
+ - scenario: "In a workspace with `docs/wiki/`, the user says 'recuerda que toda la actividad, también la de Andorra, va por una sola sociedad, Nadsan 2006 S.L.'. Show where rsc-suggest puts it."
81
+ must_include:
82
+ - "Writes the fact into the `docs/wiki/` page of its topic (creating it and linking it from `wiki/index.md` if none exists), not only into the assistant's own memory under `~/.claude/projects/`"
83
+ - "Tells the person the path where it was saved"
84
+ - "Does not start the feature chain or ask a lane question for it"
@@ -43,7 +43,7 @@ const SEMVER = /^\d+\.\d+\.\d+(?:-[0-9a-z.-]+)?$/i;
43
43
  * nothing checks is just drift with a head start.
44
44
  */
45
45
  export const PROJECT_OPT_OUTS = [
46
- 'audit', 'claudemd-check', 'danger-guard', 'feature-gate', 'gitmoji', 'ship-guard', 'worktree-cleanup',
46
+ 'audit', 'claudemd-check', 'danger-guard', 'feature-gate', 'gitmoji', 'knowledge-sync', 'ship-guard', 'trunk-guard', 'trunk-open', 'worktree-cleanup', 'worktree-isolation',
47
47
  ];
48
48
 
49
49
  /**