@ericrisco/rsc 3.0.0 → 3.0.1
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 +3 -1
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/targets/knowledge-sync.mjs +35 -5
package/README.md
CHANGED
|
@@ -442,6 +442,8 @@ new · clones main has it all after the first message
|
|
|
442
442
|
- **When a turn ends**, your changes in those folders are committed as `📝 docs(auto): … [skip ci]`
|
|
443
443
|
and sent to `rsc/knowledge` on `origin`, from whatever branch you are on. If you are on a closed
|
|
444
444
|
`main`, nothing is committed locally: the snapshot is built aside and only goes to `rsc/knowledge`.
|
|
445
|
+
Knowledge that you or the agent already **committed** on the branch during the turn goes up too —
|
|
446
|
+
only its `01-TOOLS/` and `02-DOCS/` part, under a `docs(auto) … [skip ci]` message.
|
|
445
447
|
- **Before each message**, what teammates sent is brought into **the branch you are on**, and you are
|
|
446
448
|
told in one line. Only the knowledge folders are touched.
|
|
447
449
|
- **It reaches `main` the normal way.** Your feature branch now carries the team's knowledge, so it
|
|
@@ -455,7 +457,7 @@ What it will not do, by design:
|
|
|
455
457
|
| | |
|
|
456
458
|
| --- | --- |
|
|
457
459
|
| Push to `main` | Never. Only `rsc/knowledge` receives anything. |
|
|
458
|
-
| Push your code or your unpushed commits | Only
|
|
460
|
+
| Push your code or your unpushed commits | Only the knowledge folders go up, even from a commit that also has code. Your code waits for you. |
|
|
459
461
|
| Trigger CI or a deploy | Every automatic commit says `[skip ci]`. |
|
|
460
462
|
| Bring in anybody else's code | Only knowledge paths come down. Code, config and `.claude/` are never pulled. |
|
|
461
463
|
| Sync `02-DOCS/wiki/harness/user-profile.md` | Those are one person's dials. |
|
package/manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -104,7 +104,7 @@ const worktreeBlob = (root, path) => (existsSync(join(root, path)) ? line(root,
|
|
|
104
104
|
function readState(root) {
|
|
105
105
|
let s = {};
|
|
106
106
|
try { s = JSON.parse(readFileSync(join(root, '.rsc', STATE), 'utf8')); } catch { /* first run */ }
|
|
107
|
-
return { announced: false, seen: null, queue: [], ours: [], notices: [], lastFetch: 0, snap: null, ...s };
|
|
107
|
+
return { announced: false, seen: null, queue: [], ours: [], notices: [], lastFetch: 0, snap: null, head: null, ...s };
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
function writeState(root, s) {
|
|
@@ -232,6 +232,26 @@ export function commitLocal(root, s) {
|
|
|
232
232
|
return sha;
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
+
/**
|
|
236
|
+
* Knowledge the agent (or the person) committed on the branch since the last turn goes up too. Without
|
|
237
|
+
* this only what was still uncommitted when the turn ended travelled, and an agent that commits its
|
|
238
|
+
* feature document with its code in one long turn left nothing to send (E2E 2026-10-04). The first
|
|
239
|
+
* time, and after a branch switch, it starts where the branch left the remote default branch.
|
|
240
|
+
*/
|
|
241
|
+
export function queueCommitted(root, s) {
|
|
242
|
+
const branch = line(root, ['rev-parse', '--abbrev-ref', 'HEAD']);
|
|
243
|
+
if (!branch || branch === 'HEAD') return;
|
|
244
|
+
const head = line(root, ['rev-parse', 'HEAD']);
|
|
245
|
+
const def = defaultBranch(root);
|
|
246
|
+
const remoteDef = def && line(root, ['rev-parse', '--verify', '--quiet', `refs/remotes/origin/${def}`]);
|
|
247
|
+
const valid = s.head && line(root, ['rev-parse', '--verify', '--quiet', `${s.head}^{commit}`])
|
|
248
|
+
&& run(root, ['merge-base', '--is-ancestor', s.head, head]).ok;
|
|
249
|
+
const from = valid ? s.head : (remoteDef && line(root, ['merge-base', 'HEAD', remoteDef]));
|
|
250
|
+
if (!head || !from || from === head) return;
|
|
251
|
+
const commits = git(root, ['rev-list', '--reverse', '--no-merges', `${from}..${head}`, '--', ...KNOWLEDGE]).split('\n').filter(Boolean);
|
|
252
|
+
for (const c of commits) if (!s.queue.includes(c) && !s.ours.includes(c)) s.queue.push(c);
|
|
253
|
+
}
|
|
254
|
+
|
|
235
255
|
// ------------------------------------------------------------------ network: up
|
|
236
256
|
|
|
237
257
|
/**
|
|
@@ -241,7 +261,9 @@ export function commitLocal(root, s) {
|
|
|
241
261
|
*/
|
|
242
262
|
function replay(root, base, sha, ours = []) {
|
|
243
263
|
if (!line(root, ['rev-parse', '--verify', '--quiet', `${sha}^`])) return null;
|
|
244
|
-
|
|
264
|
+
// Only the knowledge paths: a commit of the agent's own on a branch also carries code, and code never
|
|
265
|
+
// travels this way.
|
|
266
|
+
const patch = run(root, ['diff-tree', '-p', '--binary', '--full-index', `${sha}^`, sha, '--', ...KNOWLEDGE], { timeout: NET_TIMEOUT_MS });
|
|
245
267
|
if (!patch.ok) return null;
|
|
246
268
|
if (!patch.out.trim()) return line(root, ['rev-parse', `${base}^{tree}`]);
|
|
247
269
|
const env = { GIT_INDEX_FILE: join(root, '.rsc', 'knowledge-sync.index') };
|
|
@@ -250,7 +272,7 @@ function replay(root, base, sha, ours = []) {
|
|
|
250
272
|
// File by file: one that is already there as it should be is skipped, not read as a clash (a
|
|
251
273
|
// teammate's change taken uncommitted on a closed trunk comes back up in the next snapshot), and
|
|
252
274
|
// one real clash does not hold back the rest of the patch's reasoning.
|
|
253
|
-
const files = zlist(git(root, ['diff-tree', '--no-commit-id', '--name-only', '--no-renames', '-r', '-z', `${sha}^`, sha]));
|
|
275
|
+
const files = zlist(git(root, ['diff-tree', '--no-commit-id', '--name-only', '--no-renames', '-r', '-z', `${sha}^`, sha, '--', ...KNOWLEDGE])).filter(isKnowledge);
|
|
254
276
|
for (const f of files) {
|
|
255
277
|
const next = blob(root, sha, f);
|
|
256
278
|
const there = blob(root, base, f);
|
|
@@ -304,13 +326,19 @@ export function ship(root, s) {
|
|
|
304
326
|
if (!line(root, ['rev-parse', '--verify', '--quiet', `${sha}^{commit}`])) continue; // rewritten away
|
|
305
327
|
const tree = replay(root, base, sha, s.ours);
|
|
306
328
|
if (tree === null) {
|
|
307
|
-
const files = zlist(git(root, ['diff-tree', '--no-commit-id', '--name-only', '-r', '-z', sha]));
|
|
329
|
+
const files = zlist(git(root, ['diff-tree', '--no-commit-id', '--name-only', '-r', '-z', sha, '--', ...KNOWLEDGE])).filter(isKnowledge);
|
|
308
330
|
note(s, `No he podido subir ${listed(files)}: choca con un cambio que ya está en ${KNOWLEDGE_BRANCH}. ` +
|
|
309
331
|
'Tu versión sigue aquí; hay que juntarlas a mano.');
|
|
310
332
|
continue;
|
|
311
333
|
}
|
|
312
334
|
if (tree === line(root, ['rev-parse', `${base}^{tree}`])) { kept.push(sha); continue; } // already there
|
|
313
|
-
const [name, email, date, ...
|
|
335
|
+
const [name, email, date, ...rest] = git(root, ['log', '-1', '--format=%an%n%ae%n%aI%n%B', sha]).split('\n');
|
|
336
|
+
// A commit of somebody's own (code + docs) goes up as its knowledge part, under a message that
|
|
337
|
+
// says so and carries [skip ci]: its own message would describe code that is not there, and
|
|
338
|
+
// would run CI on the exchange branch.
|
|
339
|
+
const body = rest.join('\n').includes(SKIP_CI) ? rest : [
|
|
340
|
+
`📝 docs(auto): ${summary(zlist(git(root, ['diff-tree', '--no-commit-id', '--name-only', '-r', '-z', sha, '--', ...KNOWLEDGE])).filter(isKnowledge))} ${SKIP_CI}`,
|
|
341
|
+
'', `Desde: ${rest[0] || sha.slice(0, 7)}`];
|
|
314
342
|
base = git(root, ['commit-tree', tree, '-p', base, '-F', '-'], {
|
|
315
343
|
input: body.join('\n'),
|
|
316
344
|
env: { GIT_AUTHOR_NAME: name, GIT_AUTHOR_EMAIL: email, GIT_AUTHOR_DATE: date },
|
|
@@ -448,7 +476,9 @@ export function onTurn(root, { spawnShip = true } = {}) {
|
|
|
448
476
|
try {
|
|
449
477
|
if (inactiveReason(root) || busy(root)) return;
|
|
450
478
|
const pending = locked(root, (s) => {
|
|
479
|
+
try { queueCommitted(root, s); } catch { /* the next turn looks again */ }
|
|
451
480
|
try { commitLocal(root, s); } catch (e) { note(s, `La sincronización del conocimiento no pudo comitear: ${clean(e.message, 160)}`); }
|
|
481
|
+
s.head = line(root, ['rev-parse', '--verify', '--quiet', 'HEAD']) || s.head;
|
|
452
482
|
return s.queue.length;
|
|
453
483
|
});
|
|
454
484
|
if (pending && spawnShip) background(root, 'ship');
|