@amoghvj/txr 0.1.1 → 0.2.0

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
@@ -13,21 +13,13 @@ It works independently of any user Git repository, maintaining its own internal
13
13
 
14
14
  ## Installation
15
15
 
16
- Currently, `txr` is available as a local package.
16
+ `txr` is available as a global npm package.
17
17
 
18
18
  ```bash
19
- # Clone or navigate to the repository
20
- cd agent-cli
21
-
22
- # Install dependencies and build
23
- npm install
24
- npm run build
25
-
26
- # Link globally (optional, so you can run 'txr' anywhere)
27
- npm link
19
+ npm install -g @amoghvj/txr
28
20
  ```
29
21
 
30
- If you don't link it, you can run it via `node dist/index.js` or `npx .`.
22
+ Once installed, the `txr` command will be available anywhere on your system.
31
23
 
32
24
  ## Usage Instructions
33
25
 
@@ -110,6 +102,7 @@ txr history
110
102
 
111
103
  ## Advanced Options
112
104
 
105
+ * `txr clear`: Permanently reset the internal state and delete all transaction history in the active `.txr` folder. Useful if the system enters an unrecoverable state or you want to start fresh. Use `-y` to skip confirmation.
113
106
  * `txr run --transactional "cmd"`: Force a command to be tracked as transactional, even if the classifier flags it as unsafe.
114
107
  * `txr run --no-transaction "cmd"`: Force a command to run directly without tracking or generating a transaction.
115
108
 
@@ -0,0 +1,10 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ export {
9
+ __require
10
+ };
@@ -0,0 +1,185 @@
1
+ // src/core/external-detector.ts
2
+ import * as fs from "fs/promises";
3
+ import * as path from "path";
4
+ import * as crypto from "crypto";
5
+ import { nanoid } from "nanoid";
6
+ var ExternalChangeDetector = class {
7
+ constructor(txStore, fileMap, gitStore, config, projectRoot) {
8
+ this.txStore = txStore;
9
+ this.fileMap = fileMap;
10
+ this.gitStore = gitStore;
11
+ this.config = config;
12
+ this.projectRoot = projectRoot;
13
+ }
14
+ txStore;
15
+ fileMap;
16
+ gitStore;
17
+ config;
18
+ projectRoot;
19
+ async detectAndRecord() {
20
+ return this.detectAndRecordWithSource("external");
21
+ }
22
+ async detectAndRecordWithSource(source) {
23
+ const headTx = this.txStore.getHead();
24
+ if (!headTx) return null;
25
+ const divergences = await this.findDivergences(headTx);
26
+ if (divergences.length === 0) return null;
27
+ for (const div of divergences) {
28
+ if (div.type === "created" || div.type === "modified") {
29
+ try {
30
+ const content = await fs.readFile(div.absolutePath);
31
+ await this.gitStore.writeFile(div.fileId, content);
32
+ } catch {
33
+ await this.gitStore.removeFile(div.fileId).catch(() => {
34
+ });
35
+ }
36
+ } else if (div.type === "deleted") {
37
+ await this.gitStore.removeFile(div.fileId).catch(() => {
38
+ });
39
+ }
40
+ }
41
+ const isExternal = source === "external";
42
+ const txId = isExternal ? this.txStore.nextExtId() : this.txStore.nextId();
43
+ const actionType = isExternal ? "E" : "U";
44
+ const commitMsg = isExternal ? `txr: ${txId} | [external change \u2014 ${divergences.length} file(s)]` : `txr: ${txId} | passive: ${source}`;
45
+ const commitHash = await this.gitStore.commit(commitMsg);
46
+ const changeManifest = {};
47
+ for (const div of divergences) {
48
+ changeManifest[div.fileId] = div.type;
49
+ }
50
+ const tx = {
51
+ id: txId,
52
+ type: actionType,
53
+ parent: headTx.id,
54
+ commit: commitHash,
55
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
56
+ command: isExternal ? null : source,
57
+ source,
58
+ touchedFiles: divergences.map((d) => d.fileId),
59
+ changeManifest,
60
+ workingDirectory: this.projectRoot,
61
+ exitCode: isExternal ? null : 0,
62
+ attributionTier: null,
63
+ scope: this.config.scope,
64
+ scopeWarnings: []
65
+ };
66
+ for (const div of divergences) {
67
+ if (div.type === "deleted") {
68
+ const entry = this.fileMap.getEntry(div.fileId);
69
+ if (entry && !entry.deletedAt) {
70
+ this.fileMap.markDeleted(div.fileId, txId);
71
+ }
72
+ } else if (div.type === "created") {
73
+ this.fileMap.registerNewFile(div.absolutePath, div.fileId, txId);
74
+ }
75
+ }
76
+ if (isExternal) {
77
+ this.txStore.addExternalTransaction(tx);
78
+ } else {
79
+ const discarded = this.txStore.addTransaction(tx);
80
+ if (discarded.length > 0) {
81
+ this.fileMap.untrackByTransactions(discarded);
82
+ }
83
+ }
84
+ await this.txStore.save();
85
+ await this.fileMap.save();
86
+ const { HistoryStore } = await import("./history-store-2H73O75H.js");
87
+ const historyStore = await HistoryStore.load(path.join(this.projectRoot, ".txr", "metadata"));
88
+ historyStore.append({
89
+ timestamp: tx.timestamp,
90
+ command: isExternal ? null : source,
91
+ transactionId: txId,
92
+ workingDirectory: this.projectRoot,
93
+ exitCode: isExternal ? null : 0,
94
+ classification: isExternal ? "external" : "transactional",
95
+ attributionTier: null,
96
+ durationMs: 0,
97
+ actionType
98
+ });
99
+ await historyStore.save();
100
+ if (isExternal) {
101
+ process.stderr.write(
102
+ `\x1B[33m\u26A0 External changes detected.\x1B[0m ${divergences.length} file(s) modified outside TXR since ${headTx.id}.
103
+ Recorded as \x1B[1m${txId}\x1B[0m. Run \x1B[1mtxr history\x1B[0m to review.
104
+ `
105
+ );
106
+ }
107
+ return tx;
108
+ }
109
+ /**
110
+ * Find all filesystem divergences since the HEAD transaction's commit.
111
+ *
112
+ * Strategy:
113
+ * 1. For each file tracked in HEAD's commit tree → compare blob hash to disk hash.
114
+ * 2. Scan watch paths for new files not yet in FileMap.
115
+ */
116
+ async findDivergences(headTx) {
117
+ const divergences = [];
118
+ const activeFiles = this.fileMap.getActiveFiles();
119
+ for (const { id: fileId, entry } of activeFiles) {
120
+ const absolutePath = entry.path;
121
+ const existsInCommit = await this.gitStore.fileExistsAtCommit(headTx.commit, fileId);
122
+ if (!existsInCommit) continue;
123
+ let existsOnDisk = false;
124
+ let diskHash = null;
125
+ try {
126
+ const content = await fs.readFile(absolutePath);
127
+ existsOnDisk = true;
128
+ diskHash = this.hash(content);
129
+ } catch {
130
+ existsOnDisk = false;
131
+ }
132
+ if (!existsOnDisk) {
133
+ divergences.push({ fileId, absolutePath, type: "deleted" });
134
+ continue;
135
+ }
136
+ try {
137
+ const blobContent = await this.gitStore.readBlob(headTx.commit, fileId);
138
+ const blobHash = this.hash(blobContent);
139
+ if (diskHash !== blobHash) {
140
+ divergences.push({ fileId, absolutePath, type: "modified" });
141
+ }
142
+ } catch {
143
+ divergences.push({ fileId, absolutePath, type: "modified" });
144
+ }
145
+ }
146
+ try {
147
+ const { glob } = await import("glob");
148
+ const allFiles = await glob("**/*", {
149
+ cwd: this.projectRoot,
150
+ nodir: true,
151
+ absolute: true,
152
+ ignore: [
153
+ ".git/**",
154
+ ".txr/**",
155
+ "node_modules/**",
156
+ "__pycache__/**",
157
+ ".venv/**",
158
+ "venv/**",
159
+ "dist/**",
160
+ "build/**"
161
+ ]
162
+ });
163
+ for (const absolutePath of allFiles) {
164
+ if (!this.fileMap.getIdByPath(absolutePath)) {
165
+ const tempFileId = nanoid();
166
+ divergences.push({
167
+ fileId: tempFileId,
168
+ absolutePath,
169
+ type: "created"
170
+ });
171
+ }
172
+ }
173
+ } catch (err) {
174
+ console.error("Error scanning for new files:", err);
175
+ }
176
+ return divergences;
177
+ }
178
+ hash(content) {
179
+ return crypto.createHash("sha256").update(content).digest("hex");
180
+ }
181
+ };
182
+
183
+ export {
184
+ ExternalChangeDetector
185
+ };
@@ -0,0 +1,47 @@
1
+ // src/core/history-store.ts
2
+ import * as fs from "fs/promises";
3
+ import * as path from "path";
4
+ var HistoryStore = class _HistoryStore {
5
+ data;
6
+ filePath;
7
+ constructor(filePath, data) {
8
+ this.filePath = filePath;
9
+ this.data = data;
10
+ }
11
+ static async load(metadataDir) {
12
+ const filePath = path.join(metadataDir, "history.json");
13
+ try {
14
+ const raw = await fs.readFile(filePath, "utf-8");
15
+ const data = JSON.parse(raw);
16
+ return new _HistoryStore(filePath, data);
17
+ } catch {
18
+ const data = { version: 1, entries: [] };
19
+ return new _HistoryStore(filePath, data);
20
+ }
21
+ }
22
+ async save() {
23
+ const tmp = this.filePath + ".tmp";
24
+ await fs.writeFile(tmp, JSON.stringify(this.data, null, 2), "utf-8");
25
+ await fs.rename(tmp, this.filePath);
26
+ }
27
+ /** Append a new history entry. */
28
+ append(entry) {
29
+ this.data.entries.push(entry);
30
+ }
31
+ /** Get all entries (oldest first). */
32
+ getAll() {
33
+ return this.data.entries;
34
+ }
35
+ /** Get the N most recent entries. */
36
+ getRecent(n) {
37
+ return this.data.entries.slice(-n);
38
+ }
39
+ /** Get total entry count. */
40
+ get count() {
41
+ return this.data.entries.length;
42
+ }
43
+ };
44
+
45
+ export {
46
+ HistoryStore
47
+ };
@@ -1,10 +1,3 @@
1
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
- }) : x)(function(x) {
4
- if (typeof require !== "undefined") return require.apply(this, arguments);
5
- throw Error('Dynamic require of "' + x + '" is not supported');
6
- });
7
-
8
1
  // src/git/git-store.ts
9
2
  import * as fs from "fs/promises";
10
3
  import * as path from "path";
@@ -181,6 +174,5 @@ var GitStore = class {
181
174
  };
182
175
 
183
176
  export {
184
- __require,
185
177
  GitStore
186
178
  };
@@ -0,0 +1,7 @@
1
+ import {
2
+ ExternalChangeDetector
3
+ } from "./chunk-6SSBMHMQ.js";
4
+ import "./chunk-3RG5ZIWI.js";
5
+ export {
6
+ ExternalChangeDetector
7
+ };
@@ -0,0 +1,7 @@
1
+ import {
2
+ GitStore
3
+ } from "./chunk-MCJNEIJH.js";
4
+ import "./chunk-3RG5ZIWI.js";
5
+ export {
6
+ GitStore
7
+ };
@@ -0,0 +1,7 @@
1
+ import {
2
+ HistoryStore
3
+ } from "./chunk-KXCWVEEG.js";
4
+ import "./chunk-3RG5ZIWI.js";
5
+ export {
6
+ HistoryStore
7
+ };