Skip to content

Commit a8534cc

Browse files
committed
Update library examples and verify scored rename parsing
1 parent 9a2df1f commit a8534cc

3 files changed

Lines changed: 24 additions & 5 deletions

File tree

‎README.md‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Tests create disposable local repositories. They do not invoke agents, access Gi
2020

2121
- `core/plan.ts`: schema validation; YAML/JSON import; projected file-state and dependency checks; literal command parsing; individual suggestion validation and application.
2222
- `git/history.ts`: reads an immutable base-to-head commit range and file blobs. Uses argv, disables external diff/textconv helpers, hooks, and replacement objects.
23-
- `core/linking.ts`: replays line changes using an explicit `Map<commitSha, planItemId>` supplied by the caller. Trailers never establish ownership. Foreign work is Unplanned; overlapping item edits are Ambiguous; undeclared edits stay on their owner's row as out of scope.
23+
- `core/linking.ts`: replays line changes using an explicit `Map<commitSha, planItemId | null>` supplied by the caller. Trailers never establish ownership. Foreign work is Unplanned; overlapping item edits are Ambiguous; undeclared edits stay on their owner's row as out of scope.
2424
- `core/approvals.ts`: approval snapshots, dependency staleness, assignments, and accept-as-is choices keyed by content and duplicate occurrence/count.
2525

2626
Example from TypeScript (Node can load these source modules):
@@ -31,23 +31,25 @@ import { readHistory } from './git/history.ts';
3131
import { linkHistory } from './core/linking.ts';
3232

3333
const { plan, warnings } = importPlan(planText, 'yaml', {
34-
baseFiles: pathsAtBaseCommit, // Include symlink and submodule entries, never their targets.
34+
identity: storedPlanIdentity, // Stable repositoryId, taskId, and planId from storage.
35+
baseEntries: entriesAtBaseCommit, // Typed file/gitlink entries; symlinks include target text.
36+
pathKey: checkoutPathKey, // Actual checkout case/Unicode identity; fail if unknown.
3537
allowedCommands: [['npm', 'test']],
3638
issue: 412,
3739
}, nextRevision);
3840
const history = readHistory(repoPath, baseCommit, headCommit);
39-
const segments = linkHistory(plan, history, trustedCommitLedger);
41+
const segments = linkHistory(plan, history, trustedCommitLedger, checkoutPathKey);
4042
```
4143

42-
Inputs such as `planText` and the ledger must come from the caller. The future `runner/store` owns the database and ledger; this library does not infer them from commit messages. Before saving a suggested edit, the store must compare-and-swap the plan revision in one transaction. The pure `applySuggestion` function validates a copy but cannot lock storage.
44+
Inputs such as `planText` and the ledger must come from the caller. The future `runner/store` owns the database and ledger; this library does not infer them from commit messages. Before saving a suggested edit, the store must load its captured identity/revision binding by opaque suggestion ID, reject canceled or consumed IDs, and compare-and-swap the plan revision plus consume/invalidate old suggestions in one transaction. The pure `applySuggestion` function requires that trusted binding and validates a copy, but cannot lock storage or prevent replay by itself. Applying one card stales its siblings; refresh and review regenerated cards before the next Apply. Approvals and choices likewise require the stored plan identity.
4345

4446
## Current limits and safety
4547

4648
- History must be linear and descend from the requested base. Merge histories are rejected with a rebase instruction; repositories using object alternates or symlinked object storage are rejected (storage inspection is limited to 100,000 entries). Reads are bounded to 500 commits, 32 MiB per Git response, 64 MiB of unique blob bytes across the history (callers may lower `maxBlobBytes`), 8 MiB of cumulative diff output, 20,000 cumulative file records (including the final diff), and a 2-second budget per line diff; oversized work fails explicitly.
4749
- The caller selects and trusts the repository and its Git administrative directory. Normal Git discovery, linked-worktree gitfiles, and symlinked gitdirs are supported; object-storage links and alternates inside that selected gitdir are rejected. This adapter is not a filesystem-containment boundary for untrusted repository roots.
4850
- Object storage must remain unchanged during a read; these library checks do not isolate a concurrently hostile filesystem.
4951
- Ownership uses line diffs, not semantic inference. Within one replacement block, new lines inherit all affected owners conservatively. Function context comes from Git hunk headers, not an AST.
50-
- The importer requires an accurate base-file list. It rejects path traversal, Git metadata paths, and traversal through a listed file/symlink/submodule. Runtime symlink and write-scope enforcement belong to the future container/runner; plan validation alone is not a sandbox.
52+
- The importer requires accurate typed base entries, stable plan identity, a selected issue, and a trusted checkout path-identity function. It rejects path traversal, Git metadata paths, and traversal through a listed file/symlink/submodule. Runtime symlink and write-scope enforcement belong to the future container/runner; plan validation alone is not a sandbox.
5153
- Allowed commands restrict accidents, not hostile programs or changed scripts. Parsing returns argv and never executes it. An unlisted valid command is a warning and must not run until allowed.
5254
- No code here claims container isolation, vendor-only network access, credential protection, or safe dependency installation. Those controls must be implemented before running agents.
5355

‎docs/implementation/build-step-1.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,7 @@ Validation began with 60 passing tests. The added v1 regressions reproduced 18 f
9090
### Explicit remaining work
9191

9292
Issue #6 remains open for runner/store integration: obtain typed base entries and actual filesystem identity from a trusted checkout; audit actual occupancy, new symlinks/conversions, link targets and target mutations after execution; provide persistent request IDs, cancellation, replay prevention and concurrent CAS; enforce prompt budgets/profiles, output limits, container mounts, and process termination. The library only checks declared/projected state and trusted supplied context. In particular an edit may repair an unsafe existing link, but only the future runtime audit can validate its new target and accepted filesystem state. No application, runtime safety boundary, or concurrent store has been added here. Issues #2 and #3 remain the next approved build steps.
93+
94+
## Merged-contract review round 1
95+
96+
Updated README import/linking examples to supply the new required context. Declined the scored-rename report: the raw-diff regex captures only `([A-Z])` and consumes the score separately with `\d*`, so the existing branch receives `R`, not `R100`. A focused real-Git test observed a scored rename followed by an added-file record and loaded both correctly before any parser change. Retained that regression. A separate locally discovered C1-control regression failed first, then passed after using the complete Unicode control category for commands/paths.

‎test/history.test.ts‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,3 +257,16 @@ it('records typed object identities on real mode-change cards', () => {
257257
expect(metadata.oldObject.oid).toBe(metadata.newObject.oid);
258258
expect(metadata.oldMode).toBe('100644'); expect(metadata.newMode).toBe('100755');
259259
});
260+
261+
it('parses scored renames and consumes both paths before the next raw record', () => {
262+
const initial = Array.from({ length: 100 }, (_, i) => `line ${i}\n`).join('');
263+
const f = fixture({ 'a.txt': initial });
264+
renameSync(join(f.dir, 'a.txt'), join(f.dir, 'b.txt'));
265+
f.write('b.txt', initial.replace('line 50\n', 'changed 50\n'));
266+
f.write('z.txt', 'another record\n'); const head = f.commit('P1');
267+
const raw = f.git('diff', '--raw', '-z', '-M', f.base, head);
268+
expect(raw).toMatch(/ R\d+\0a\.txt\0b\.txt\0/u);
269+
const history = readHistory(f.dir, f.base);
270+
expect(history.final.some(d => d.oldPath === 'a.txt' && d.newPath === 'b.txt')).toBe(true);
271+
expect(history.final.some(d => d.oldPath === null && d.newPath === 'z.txt')).toBe(true);
272+
});

0 commit comments

Comments
 (0)