anchorgit-cli 0.1.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/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,137 @@
1
+ # ⚓ AnchorGit CLI (`anchorgit`)
2
+
3
+ <p align="center">
4
+ <b>Local-first, zero-knowledge decision notary and human intent engine for Git workflows.</b>
5
+ </p>
6
+
7
+ <p align="center">
8
+ <a href="https://opensource.org/licenses/Apache-2.0"><img src="https://img.shields.io/badge/License-Apache_2.0-blue.svg" alt="License"></a>
9
+ <a href="https://www.npmjs.com/package/anchorgit"><img src="https://img.shields.io/badge/npm-v1.0.0-emerald.svg" alt="npm version"></a>
10
+ <a href="https://node.js.org"><img src="https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen.svg" alt="Node.js"></a>
11
+ </p>
12
+
13
+ ---
14
+
15
+ ## 💡 Why AnchorGit?
16
+
17
+ AI writes code at lightspeed. **AnchorGit proves why you built it.**
18
+
19
+ Git records *what* changed in your source code. AnchorGit notarizes *why* it changed—attaching verifiable trade-off rationale, architectural intent, and hardware-bound HMAC signatures to your commits **without ever exposing your raw source code**.
20
+
21
+ ```
22
+ ┌────────────────┐ ┌────────────────────┐ ┌──────────────────────┐
23
+ │ Local Git Diff │ ───► │ Local SHA-256 Hash │ ───► │ Hardware HMAC Sign │
24
+ └────────────────┘ └────────────────────┘ └──────────┬───────────┘
25
+
26
+ Zero Source Code Transmitted ▼
27
+ ┌──────────────────────┐
28
+ │ Cloud Decision Log │
29
+ └──────────────────────┘
30
+ ```
31
+
32
+ ---
33
+
34
+ ## ⚡ Quickstart
35
+
36
+ ### 1. Installation
37
+
38
+ Install globally using `npm`:
39
+
40
+ ```bash
41
+ npm install -g anchorgit
42
+ ```
43
+
44
+ Or run directly with `npx`:
45
+
46
+ ```bash
47
+ npx anchorgit --help
48
+ ```
49
+
50
+ ### 2. Pair Your Workstation
51
+
52
+ Authenticate your local CLI with your AnchorGit developer dashboard using your personal API key:
53
+
54
+ ```bash
55
+ anchor pair <YOUR_API_KEY>
56
+ ```
57
+
58
+ This derives a local hardware fingerprint, generates a secure salt, and persists config locally to `~/.anchor/config.json`.
59
+
60
+ ### 3. Record an Architectural Decision
61
+
62
+ Log architectural trade-offs or steering decisions directly alongside your working directory changes:
63
+
64
+ ```bash
65
+ anchor decide "Refactored auth token rotation to use Redis instead of in-memory store"
66
+ ```
67
+
68
+ ---
69
+
70
+ ### 💡 When to Notarize (`anchor decide`)
71
+
72
+ Reserve `anchor decide` for high-leverage inflection points:
73
+
74
+ * **🟢 Do Notarize:** Architectural trade-offs, AI course corrections, security shifts, breaking schema changes.
75
+ * **🔴 Skip:** Typo fixes, CSS tweaks, linting, or routine dependency bumps.
76
+
77
+ ## 💻 Command Reference
78
+
79
+ | Command | Usage | Description |
80
+ | :--- | :--- | :--- |
81
+ | **`pair`** | `anchor pair <api-key>` | Binds workstation hardware fingerprint & persists API credentials. |
82
+ | **`decide`** | `anchor decide "<message>"` | Hashes local diffs, signs payload via hardware HMAC, and syncs log. |
83
+ | **`log`** | `anchor log` | Displays formatted local decision ledger history. |
84
+ | **`context`** | `anchor context [path]` | Displays recent architectural decisions for a file or project. |
85
+ | **`pr`** | `anchor pr` | Generates a Markdown Pull Request Decision Brief for reviewers. |
86
+ | **`standup`** | `anchor standup` | Compiles recent decisions into a clean daily standup report. |
87
+ | **`whoami`** | `anchor whoami` | Displays currently paired developer identity and public profile link. |
88
+
89
+ ---
90
+
91
+ ### 🎛️ `anchor decide` Options
92
+
93
+ * `-c, --category <type>` — Category of decision (e.g. `architecture`, `ai-steering`, `security`; default: `architecture`).
94
+ * `-d, --deliberation <seconds>` — Deliberation focus time in seconds (default: `0`).
95
+ * `--dry-run` — Print the exact zero-knowledge JSON payload to stdout without sending.
96
+
97
+ ---
98
+
99
+ ## 🛡️ Enterprise Security & Zero-Knowledge Guarantee
100
+
101
+ Built for security-conscious teams, CISOs, and enterprise compliance:
102
+
103
+ * **Zero Source Code Ingestion:** AnchorGit **never** reads, transmits, or stores raw source code or environment secrets.
104
+ * **Local SHA-256 Hashing:** Diffs are parsed strictly on your local machine, hashed into `diff_sha256`, and discarded immediately.
105
+ * **Hardware HMAC Signatures:** Decision payloads are cryptographically signed using your workstation CPU/board identity and salt to prevent machine impersonation.
106
+ * **Inspect Payloads (`--dry-run`):** Inspect the exact JSON output before any network call occurs:
107
+
108
+ ```bash
109
+ anchor decide "Auditing security payload" --dry-run
110
+ ```
111
+
112
+ ```json
113
+ {
114
+ "id": "4a4303c8-925d-4206-a691-1194ea140874",
115
+ "workstation_guid": "7bfd571acc9886bc3fae5a99888bbde8",
116
+ "timestamp": "2026-09-11T06:51:11.439Z",
117
+ "decision_summary": "Auditing security payload",
118
+ "category": "architecture",
119
+ "commit_sha": "7f97135c5cc93b9016ebd408a621519b4a312fb8",
120
+ "branch": "main",
121
+ "lines_added": 30,
122
+ "lines_deleted": 24,
123
+ "diff_sha256": "936a849f40f174bc4c28202ecfd37563337148d15c484577649d38d90980efd8",
124
+ "affected_files": [
125
+ "src/index.ts"
126
+ ],
127
+ "deliberation_seconds": 0,
128
+ "deliberation_source": "cli_user_declared",
129
+ "hmac_signature": "16abaef5c6a6e3c509f39a5e0d49b518c3379863aef1c0b786bc4d3eb15013c5"
130
+ }
131
+ ```
132
+
133
+ ---
134
+
135
+ ## 📄 License
136
+
137
+ Distributed under the **Apache 2.0 License**. See [`LICENSE`](./LICENSE) for details.
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sendDecision = sendDecision;
4
+ const config_js_1 = require("../core/config.js");
5
+ async function sendDecision(payload) {
6
+ const config = (0, config_js_1.getConfig)();
7
+ // If the user hasn't run `anchor pair <api-key>`, skip cloud sync silently
8
+ if (!config.apiKey) {
9
+ console.log(`🔒 Local Notary Mode Active: Decision saved to ~/.anchor/ledger.json`);
10
+ console.log(`💡 Tip: Pair your workstation to sync with your web dashboard:`);
11
+ console.log(` 👉 anchor pair <your-api-key>\n`);
12
+ return false;
13
+ }
14
+ const apiUrl = config.apiUrl || process.env.ANCHORGIT_API_URL || 'https://api.anchorgit.com';
15
+ try {
16
+ const response = await fetch(`${apiUrl}/api/v1/decide`, {
17
+ method: 'POST',
18
+ headers: {
19
+ 'Content-Type': 'application/json',
20
+ 'Authorization': `Bearer ${config.apiKey}`,
21
+ 'User-Agent': 'AnchorGit-CLI/0.1.0',
22
+ },
23
+ // Fix: Wrap payload in the root `decision` key expected by Rails params.require(:decision)
24
+ body: JSON.stringify({
25
+ decision: payload
26
+ }),
27
+ });
28
+ if (!response.ok) {
29
+ throw new Error(`HTTP error ${response.status}`);
30
+ }
31
+ console.log(`🌐 Cloud Sync Active: Decision notarized and synced with @${config.username || 'developer'}`);
32
+ console.log(` 👉 https://anchorgit.com/p/${config.username || 'developer'}\n`);
33
+ return true;
34
+ }
35
+ catch (error) {
36
+ console.log('\nℹ️ Cloud Sync Skipped: Backend API currently offline or unreachable.');
37
+ console.log(' (Local decision record saved successfully)\n');
38
+ return false;
39
+ }
40
+ }
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.handleContext = handleContext;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const node_os_1 = __importDefault(require("node:os"));
10
+ const LEDGER_PATH = node_path_1.default.join(node_os_1.default.homedir(), '.anchor', 'ledger.json');
11
+ /**
12
+ * Returns the dashboard verification badge for a provenance source value.
13
+ * Handles undefined gracefully for ledger entries written before this feature.
14
+ */
15
+ function getProvenanceBadge(source) {
16
+ switch (source) {
17
+ case 'ide_passive_tracker':
18
+ return '🛡️ Verified Focus';
19
+ case 'cli_user_declared':
20
+ return '📝 Self-Declared';
21
+ default:
22
+ return '🔹 Unknown';
23
+ }
24
+ }
25
+ async function handleContext(targetPath) {
26
+ try {
27
+ if (!node_fs_1.default.existsSync(LEDGER_PATH)) {
28
+ console.log('\n⚓ No local decision history found in ~/.anchor/ledger.json.');
29
+ console.log(' Run `anchor decide "..."` to notarize your first architectural trade-off!');
30
+ return;
31
+ }
32
+ const rawData = node_fs_1.default.readFileSync(LEDGER_PATH, 'utf-8');
33
+ const entries = JSON.parse(rawData);
34
+ if (entries.length === 0) {
35
+ console.log('\n⚓ Decision ledger is empty.');
36
+ return;
37
+ }
38
+ const filtered = targetPath
39
+ ? entries.filter(e => e.affected_files?.some(f => f.includes(targetPath)))
40
+ : entries;
41
+ console.log(`\n⚓ AnchorGit Decision Context ${targetPath ? `for [${targetPath}]` : '(All Projects)'}:`);
42
+ console.log('─'.repeat(65));
43
+ if (filtered.length === 0) {
44
+ console.log(` No signed architectural decisions recorded specifically for "${targetPath}".`);
45
+ return;
46
+ }
47
+ filtered.slice(-5).reverse().forEach(entry => {
48
+ const badge = getProvenanceBadge(entry.deliberation_source);
49
+ console.log(` • [${entry.commit_sha.substring(0, 7)}] (${entry.branch}) - ${new Date(entry.timestamp).toLocaleString()}`);
50
+ console.log(` Intent: "${entry.decision_summary}"`);
51
+ console.log(` Provenance: ${badge}`);
52
+ console.log(` HMAC Sign: ${entry.hmac_signature.substring(0, 16)}... [Hardware Verified]`);
53
+ console.log(` Scope: +${entry.lines_added} / -${entry.lines_deleted} LOC`);
54
+ console.log('─'.repeat(65));
55
+ });
56
+ }
57
+ catch (error) {
58
+ console.error(`❌ Error reading decision context: ${error.message}`);
59
+ }
60
+ }
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.handleDecide = handleDecide;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const node_os_1 = __importDefault(require("node:os"));
10
+ const node_crypto_1 = require("node:crypto");
11
+ const git_js_1 = require("../core/git.js");
12
+ const client_js_1 = require("../api/client.js");
13
+ const crypto_js_1 = require("../core/crypto.js");
14
+ const config_js_1 = require("../core/config.js");
15
+ const LEDGER_DIR = node_path_1.default.join(node_os_1.default.homedir(), '.anchor');
16
+ const LEDGER_FILE = node_path_1.default.join(LEDGER_DIR, 'ledger.json');
17
+ /**
18
+ * Maps a ProvenanceSource to its human-readable dashboard verification badge.
19
+ *
20
+ * - ide_passive_tracker → 🛡️ Verified Focus (hardware HMAC + passive timer)
21
+ * - cli_user_declared → 📝 Self-Declared (manually entered duration)
22
+ */
23
+ function getProvenanceBadge(source) {
24
+ switch (source) {
25
+ case 'ide_passive_tracker':
26
+ return '🛡️ Verified Focus';
27
+ case 'cli_user_declared':
28
+ return '📝 Self-Declared';
29
+ }
30
+ }
31
+ /**
32
+ * Persists the signed decision payload locally in ~/.anchor/ledger.json
33
+ * so context, pr, and standup commands can read it offline.
34
+ */
35
+ function saveToLocalLedger(entry) {
36
+ if (!node_fs_1.default.existsSync(LEDGER_DIR)) {
37
+ node_fs_1.default.mkdirSync(LEDGER_DIR, { recursive: true });
38
+ }
39
+ let entries = [];
40
+ if (node_fs_1.default.existsSync(LEDGER_FILE)) {
41
+ try {
42
+ const content = node_fs_1.default.readFileSync(LEDGER_FILE, 'utf-8');
43
+ entries = JSON.parse(content);
44
+ }
45
+ catch {
46
+ entries = [];
47
+ }
48
+ }
49
+ entries.push(entry);
50
+ node_fs_1.default.writeFileSync(LEDGER_FILE, JSON.stringify(entries, null, 2), 'utf-8');
51
+ }
52
+ async function handleDecide(message, options) {
53
+ try {
54
+ if (!(0, git_js_1.isGitRepository)()) {
55
+ console.error('❌ Error: Not inside a valid Git repository.');
56
+ process.exit(1);
57
+ }
58
+ const gitStats = (0, git_js_1.getLocalGitStats)();
59
+ const config = (0, config_js_1.getConfig)();
60
+ const workstationGuid = config.workstationGuid || (0, config_js_1.getOrGenerateWorkstationGuid)();
61
+ const rawDeliberation = options.deliberationSeconds ?? options.deliberation ?? 0;
62
+ const deliberationSeconds = typeof rawDeliberation === 'string' ? parseInt(rawDeliberation, 10) : rawDeliberation;
63
+ // Resolve provenance source — default to cli_user_declared for terminal invocations.
64
+ // The VS Code extension passes --source ide_passive_tracker when it has passive timer proof.
65
+ const deliberationSource = options.source ?? 'cli_user_declared';
66
+ const payload = {
67
+ id: (0, node_crypto_1.randomUUID)(),
68
+ workstation_guid: workstationGuid, // MUST BE INCLUDED
69
+ timestamp: new Date().toISOString(),
70
+ decision_summary: message,
71
+ category: options.category || 'architecture',
72
+ commit_sha: gitStats.commitSha,
73
+ branch: gitStats.branch,
74
+ lines_added: gitStats.linesAdded,
75
+ lines_deleted: gitStats.linesDeleted,
76
+ diff_sha256: gitStats.diffHash,
77
+ affected_files: gitStats.affectedFiles || [],
78
+ deliberation_seconds: deliberationSeconds,
79
+ // Provenance tag: included in the HMAC canonical string — cannot be mutated post-sign.
80
+ deliberation_source: deliberationSource,
81
+ };
82
+ // Computes HMAC-SHA256 over the canonical string that includes deliberation_source.
83
+ // Changing the provenance tag after signing will invalidate the signature.
84
+ const hmacSignature = (0, crypto_js_1.computeHmacSignature)(payload);
85
+ const fullPayload = {
86
+ ...payload,
87
+ hmac_signature: hmacSignature,
88
+ };
89
+ const badge = getProvenanceBadge(deliberationSource);
90
+ if (options.dryRun) {
91
+ console.log('\n🔍 [DRY RUN] Zero-Knowledge Hardware-Bound Payload:');
92
+ console.log(JSON.stringify(fullPayload, null, 2));
93
+ console.log(`\n${badge} — deliberation_source: "${deliberationSource}"`);
94
+ console.log('✅ Payload verified. No source code or secrets present.\n');
95
+ process.exit(0);
96
+ }
97
+ // 1. Save Locally to ~/.anchor/ledger.json
98
+ saveToLocalLedger(fullPayload);
99
+ console.log(`\n⚓ AnchorGit Decision Recorded!`);
100
+ console.log(` ├─ Intent: "${message}"`);
101
+ console.log(` ├─ Scope: ${gitStats.commitSha.substring(0, 7)} (${gitStats.branch}) | +${gitStats.linesAdded} / -${gitStats.linesDeleted} LOC`);
102
+ console.log(` ├─ Proof: SHA-256 (${gitStats.diffHash.substring(0, 12)}...)`);
103
+ console.log(` ├─ HMAC Sign: ${hmacSignature.substring(0, 16)}... [Hardware Bound]`);
104
+ console.log(` └─ Provenance: ${badge}`);
105
+ console.log(`\n🔒 Security Check: Zero source code transmitted. Signed via workstation hardware fingerprint.`);
106
+ if (config.username) {
107
+ console.log(`🌐 View your Verified Developer Profile & Decision Graph:`);
108
+ console.log(` 👉 https://anchorgit.com/p/${config.username}\n`);
109
+ }
110
+ else {
111
+ console.log(`💡 Tip: Pair your workstation to sync with your web dashboard:`);
112
+ console.log(` 👉 anchor pair <your-api-key>\n`);
113
+ }
114
+ // 2. Cloud Sync (Wrapped under { decision: ... } for Rails strong parameters)
115
+ await (0, client_js_1.sendDecision)(fullPayload);
116
+ process.exit(0);
117
+ }
118
+ catch (err) {
119
+ console.error(`❌ Error recording decision: ${err.message}`);
120
+ process.exit(1);
121
+ }
122
+ }
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.handleLog = handleLog;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const node_os_1 = __importDefault(require("node:os"));
10
+ const LEDGER_PATH = node_path_1.default.join(node_os_1.default.homedir(), '.anchor', 'ledger.json');
11
+ function handleLog() {
12
+ if (!node_fs_1.default.existsSync(LEDGER_PATH)) {
13
+ console.log('\n⚓ No decision history found. Run `anchor decide "..."` to record your first decision.\n');
14
+ return;
15
+ }
16
+ try {
17
+ const raw = node_fs_1.default.readFileSync(LEDGER_PATH, 'utf-8');
18
+ const entries = JSON.parse(raw);
19
+ if (!Array.isArray(entries) || entries.length === 0) {
20
+ console.log('\n⚓ Decision ledger is empty.\n');
21
+ return;
22
+ }
23
+ console.log(`\n⚓ AnchorGit Decision Ledger (${entries.length} entries):`);
24
+ console.log('═'.repeat(70));
25
+ entries.forEach((e, idx) => {
26
+ const sha = e.commit_sha ? e.commit_sha.substring(0, 7) : 'N/A';
27
+ const date = e.timestamp ? new Date(e.timestamp).toLocaleString() : 'N/A';
28
+ const category = e.category || 'architecture';
29
+ const branch = e.branch || 'unknown';
30
+ console.log(`${idx + 1}. [${sha}] (${branch}) [${category}] - ${date}`);
31
+ console.log(` Intent: "${e.decision_summary}"`);
32
+ if (e.hmac_signature) {
33
+ console.log(` HMAC Sign: ${e.hmac_signature.substring(0, 16)}... [Hardware Bound]`);
34
+ }
35
+ if (e.diff_sha256) {
36
+ console.log(` Diff Hash: SHA-256 (${e.diff_sha256.substring(0, 12)}...)`);
37
+ }
38
+ console.log('─'.repeat(70));
39
+ });
40
+ }
41
+ catch (err) {
42
+ console.error(`❌ Error reading decision ledger: ${err.message}`);
43
+ }
44
+ }
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.handlePair = handlePair;
7
+ const node_os_1 = __importDefault(require("node:os"));
8
+ const config_js_1 = require("../core/config.js");
9
+ const crypto_js_1 = require("../core/crypto.js");
10
+ async function handlePair(token) {
11
+ try {
12
+ if (!token || token.trim().length === 0) {
13
+ console.error('❌ Error: Token is required. Run `anchor pair <your-api-key>`');
14
+ process.exit(1);
15
+ }
16
+ const cleanToken = token.trim();
17
+ const config = (0, config_js_1.getConfig)();
18
+ const apiUrl = config.apiUrl || process.env.ANCHORGIT_API_URL || 'https://api.anchorgit.com';
19
+ // 1. Derive workstation GUID from physical hardware
20
+ const hardwareSecret = (0, crypto_js_1.getHardwareDerivedSecret)();
21
+ const workstationGuid = hardwareSecret.toString('hex').substring(0, 32);
22
+ console.log(`\n⚓ Pairing Workstation with AnchorGit Cloud...`);
23
+ console.log(` ├─ Workstation ID: ${workstationGuid.substring(0, 12)}...`);
24
+ console.log(` └─ Endpoint: ${apiUrl}`);
25
+ // 2. Call Rails Backend API
26
+ const response = await fetch(`${apiUrl}/api/v1/pair`, {
27
+ method: 'POST',
28
+ headers: {
29
+ 'Content-Type': 'application/json',
30
+ 'Authorization': `Bearer ${cleanToken}`,
31
+ },
32
+ body: JSON.stringify({
33
+ workstation_guid: workstationGuid,
34
+ hostname: node_os_1.default.hostname(),
35
+ platform: node_os_1.default.platform(),
36
+ arch: node_os_1.default.arch(),
37
+ }),
38
+ });
39
+ if (!response.ok) {
40
+ throw new Error(`Server returned HTTP ${response.status}`);
41
+ }
42
+ const data = await response.json();
43
+ const username = data.username || 'developer';
44
+ // 3. Persist pairing state locally using saveConfig
45
+ (0, config_js_1.saveConfig)({
46
+ apiKey: cleanToken,
47
+ username: username,
48
+ workstationGuid: workstationGuid,
49
+ apiUrl: apiUrl,
50
+ pairedAt: new Date().toISOString(),
51
+ });
52
+ console.log(`\n✅ Workstation Paired Successfully!`);
53
+ console.log(` Welcome, @${username}! Credentials saved to ~/.anchor/config.json`);
54
+ console.log(` 🌐 View your public profile: https://anchorgit.com/p/${username}`);
55
+ console.log(` You can now run \`anchor decide "your message"\` to notarize decisions.\n`);
56
+ process.exit(0);
57
+ }
58
+ catch (err) {
59
+ console.error(`❌ Pairing failed: ${err.message}`);
60
+ process.exit(1);
61
+ }
62
+ }
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.handlePr = handlePr;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const node_os_1 = __importDefault(require("node:os"));
10
+ const git_js_1 = require("../core/git.js");
11
+ const config_js_1 = require("../core/config.js");
12
+ const LEDGER_PATH = node_path_1.default.join(node_os_1.default.homedir(), '.anchor', 'ledger.json');
13
+ async function handlePr() {
14
+ try {
15
+ const gitStats = (0, git_js_1.getLocalGitStats)();
16
+ const config = (0, config_js_1.getConfig)();
17
+ const username = config.username || 'developer';
18
+ let recentDecisions = [];
19
+ if (node_fs_1.default.existsSync(LEDGER_PATH)) {
20
+ const rawData = node_fs_1.default.readFileSync(LEDGER_PATH, 'utf-8');
21
+ const entries = JSON.parse(rawData);
22
+ // Filter for current branch and deduplicate by decision_summary
23
+ const branchEntries = entries.filter((e) => e.branch === gitStats.branch);
24
+ const uniqueMap = new Map();
25
+ for (const entry of branchEntries) {
26
+ uniqueMap.set(entry.decision_summary, entry);
27
+ }
28
+ recentDecisions = Array.from(uniqueMap.values()).slice(-5);
29
+ }
30
+ const prMarkdown = `## ⚓ AnchorGit Verified Architectural Decision Brief
31
+
32
+ > 🔒 **Zero-Knowledge Audit Guarantee:** Hardware-bound HMAC verification. Zero proprietary source code ingested or transmitted.
33
+
34
+ ### 📊 Cryptographic Provenance & Change Scope
35
+ | Metric | Verified Value | Compliance Mapping |
36
+ | :--- | :--- | :--- |
37
+ | **Target Commit & Branch** | \`${gitStats.branch}\` (\`${gitStats.commitSha.substring(0, 7)}\`) | **SOC 2 CC6.1** (Change Control) |
38
+ | **Cumulative Scope** | \`+${gitStats.linesAdded} / -${gitStats.linesDeleted} LOC\` | **ISO 27001 A.12.1.2** (Capacity & Scope) |
39
+ | **Files Modified** | \`${gitStats.affectedFiles ? gitStats.affectedFiles.length : 0} files\` | **Audit Trail** (Scope Validation) |
40
+ | **Hardware Attestation** | HMAC-SHA256 (Workstation Bound) | **Non-Repudiation** (Hardware Fingerprint) |
41
+ | **Segregation of Duties** | Enforced via GitHub Branch Rules | **SOC 2 CC6.8** (Author ≠ Approver) |
42
+
43
+ ---
44
+
45
+ ### 🎯 Notarized Architectural Intent & Trade-offs
46
+
47
+ ${recentDecisions.length > 0
48
+ ? recentDecisions
49
+ .map((d, idx) => `#### ${idx + 1}. "${d.decision_summary}"
50
+ * **Diff Checksum:** \`SHA-256 (${d.diff_sha256.substring(0, 12)}...)\`
51
+ * **Hardware Attestation:** \`HMAC-SHA256 (${d.hmac_signature.substring(0, 16)}...)\` [Hardware Bound]`)
52
+ .join('\n\n')
53
+ : '*No architectural decisions notarized for this branch yet. Run `anchor decide "your intent"` to attach context.*'}
54
+
55
+ ---
56
+
57
+ ### 🛡️ Compliance & Audit Verification Instructions for Reviewer
58
+
59
+ To satisfy **SOC 2 Type II (CC6.8)** & **ISO 27001 (A.12.1.2)** single-reviewer approval requirements:
60
+
61
+ 1. **Verify Non-Author Identity:** Ensure your GitHub account is **different** from commit author \`${gitStats.commitSha.substring(0, 7)}\`.
62
+ 2. **Inspect Hardware Provenance:** Click the verification link below to confirm the workstation HMAC signature matches the registered hardware ledger.
63
+ 3. **Approve via GitHub UI:** Submit a formal "Approve" review in GitHub. GitHub's immutable audit log records the non-author approval event required by compliance frameworks.
64
+
65
+ 👉 **Auditor Live Verification Link:** [anchorgit.com/p/${username}](https://anchorgit.com/p/${username})`;
66
+ console.log(`\n` + prMarkdown + `\n`);
67
+ console.log(`💡 Tip: Paste the Markdown above directly into your Pull Request description!\n`);
68
+ process.exit(0);
69
+ }
70
+ catch (error) {
71
+ console.error(`❌ Error generating PR brief: ${error.message}`);
72
+ process.exit(1);
73
+ }
74
+ }
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.handleStandup = handleStandup;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const node_os_1 = __importDefault(require("node:os"));
10
+ const LEDGER_PATH = node_path_1.default.join(node_os_1.default.homedir(), '.anchor', 'ledger.json');
11
+ async function handleStandup() {
12
+ try {
13
+ if (!node_fs_1.default.existsSync(LEDGER_PATH)) {
14
+ console.log('\n⚓ No decision history found. Run `anchor decide "..."` to start recording.');
15
+ return;
16
+ }
17
+ const rawData = node_fs_1.default.readFileSync(LEDGER_PATH, 'utf-8');
18
+ const entries = JSON.parse(rawData);
19
+ // Filter decisions made in the last 24 hours
20
+ const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
21
+ const recent = entries.filter((e) => new Date(e.timestamp) >= oneDayAgo);
22
+ console.log(`\n📊 Yesterday's Signed Engineering Impact & Decisions:`);
23
+ console.log('═'.repeat(60));
24
+ if (recent.length === 0) {
25
+ console.log(' No architectural decisions notarized in the last 24 hours.');
26
+ console.log(' Showing last 3 overall decisions:\n');
27
+ entries.slice(-3).forEach((e) => {
28
+ console.log(` • [${e.commit_sha.substring(0, 7)}] ${e.decision_summary} (+${e.lines_added}/-${e.lines_deleted} LOC)`);
29
+ });
30
+ return;
31
+ }
32
+ recent.forEach((e) => {
33
+ console.log(` • [${e.commit_sha.substring(0, 7)}] ${e.decision_summary}`);
34
+ console.log(` └─ Scope: +${e.lines_added} / -${e.lines_deleted} LOC | HMAC: ${e.hmac_signature.substring(0, 12)}...`);
35
+ });
36
+ console.log('═'.repeat(60));
37
+ console.log(`✨ Total Impact: ${recent.length} Notarized Architectural Decisions.`);
38
+ }
39
+ catch (error) {
40
+ console.error(`❌ Error generating standup: ${error.message}`);
41
+ }
42
+ }
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleWhoami = handleWhoami;
4
+ // src/commands/whoami.ts
5
+ const config_js_1 = require("../core/config.js");
6
+ function handleWhoami() {
7
+ const config = (0, config_js_1.getConfig)();
8
+ if (!config.apiKey || !config.username) {
9
+ console.log(`\n🔒 Local Unpaired Mode`);
10
+ console.log(` No account linked yet. Run \`anchor pair <api-key>\` to connect your profile.\n`);
11
+ process.exit(0);
12
+ }
13
+ console.log(`\n⚓ AnchorGit Workstation Identity:`);
14
+ console.log(` ├─ Username: @${config.username}`);
15
+ console.log(` ├─ Workstation: ${config.workstationGuid ? config.workstationGuid.substring(0, 12) + '...' : 'Local'}`);
16
+ console.log(` ├─ Profile: https://anchorgit.com/p/${config.username}`);
17
+ console.log(` └─ Paired At: ${config.pairedAt || 'N/A'}\n`);
18
+ process.exit(0);
19
+ }
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getConfig = getConfig;
7
+ exports.getOrGenerateWorkstationGuid = getOrGenerateWorkstationGuid;
8
+ exports.saveConfig = saveConfig;
9
+ const node_fs_1 = __importDefault(require("node:fs"));
10
+ const node_path_1 = __importDefault(require("node:path"));
11
+ const node_os_1 = __importDefault(require("node:os"));
12
+ const CONFIG_DIR = node_path_1.default.join(node_os_1.default.homedir(), '.anchor');
13
+ const CONFIG_FILE = node_path_1.default.join(CONFIG_DIR, 'config.json');
14
+ function getConfig() {
15
+ if (!node_fs_1.default.existsSync(CONFIG_FILE)) {
16
+ return {
17
+ apiUrl: process.env.ANCHORGIT_API_URL || 'https://api.anchorgit.com',
18
+ };
19
+ }
20
+ try {
21
+ const rawData = node_fs_1.default.readFileSync(CONFIG_FILE, 'utf-8');
22
+ const parsed = JSON.parse(rawData);
23
+ return {
24
+ apiKey: parsed.apiKey,
25
+ apiUrl: process.env.ANCHORGIT_API_URL || parsed.apiUrl || 'https://api.anchorgit.com',
26
+ username: parsed.username || 'developer',
27
+ workstationGuid: parsed.workstationGuid,
28
+ pairedAt: parsed.pairedAt,
29
+ };
30
+ }
31
+ catch {
32
+ return {
33
+ apiUrl: process.env.ANCHORGIT_API_URL || 'https://api.anchorgit.com',
34
+ };
35
+ }
36
+ }
37
+ function getOrGenerateWorkstationGuid() {
38
+ const config = getConfig();
39
+ if (config.workstationGuid) {
40
+ return config.workstationGuid;
41
+ }
42
+ // Import dynamically or get hardware secret
43
+ const { getHardwareDerivedSecret } = require('./crypto.js');
44
+ const hardwareSecret = getHardwareDerivedSecret();
45
+ const guid = hardwareSecret.toString('hex').substring(0, 32);
46
+ saveConfig({ workstationGuid: guid });
47
+ return guid;
48
+ }
49
+ function saveConfig(newConfig) {
50
+ try {
51
+ if (!node_fs_1.default.existsSync(CONFIG_DIR)) {
52
+ node_fs_1.default.mkdirSync(CONFIG_DIR, { recursive: true });
53
+ }
54
+ const currentConfig = getConfig();
55
+ const updatedConfig = {
56
+ ...currentConfig,
57
+ ...newConfig,
58
+ };
59
+ node_fs_1.default.writeFileSync(CONFIG_FILE, JSON.stringify(updatedConfig, null, 2), 'utf-8');
60
+ }
61
+ catch (err) {
62
+ throw new Error(`Failed to save configuration: ${err.message}`);
63
+ }
64
+ }
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getHardwareDerivedSecret = getHardwareDerivedSecret;
7
+ exports.computeHmacSignature = computeHmacSignature;
8
+ const node_crypto_1 = __importDefault(require("node:crypto"));
9
+ const node_os_1 = __importDefault(require("node:os"));
10
+ const node_child_process_1 = require("node:child_process");
11
+ /**
12
+ * Extracts native hardware system UUID across operating systems.
13
+ * Satisfies Claim 3: System machine identifier extraction from kernel layer.
14
+ */
15
+ function getSystemMachineUuid() {
16
+ try {
17
+ const platform = node_os_1.default.platform();
18
+ if (platform === 'darwin') {
19
+ return (0, node_child_process_1.execSync)('ioreg -rd1 -c IOPlatformExpertDevice | grep IOPlatformUUID', { encoding: 'utf-8' })
20
+ .split('"')[3] || 'mac-fallback-uuid';
21
+ }
22
+ else if (platform === 'win32') {
23
+ return (0, node_child_process_1.execSync)('wmic csproduct get uuid', { encoding: 'utf-8' })
24
+ .split('\n')[1]
25
+ .trim() || 'win-fallback-uuid';
26
+ }
27
+ else if (platform === 'linux') {
28
+ return (0, node_child_process_1.execSync)('cat /etc/machine-id || cat /var/lib/dbus/machine-id', { encoding: 'utf-8' })
29
+ .trim() || 'linux-fallback-uuid';
30
+ }
31
+ }
32
+ catch {
33
+ // Fallback gracefully if permission restricted
34
+ }
35
+ return 'anchor-generic-kernel-uuid';
36
+ }
37
+ /**
38
+ * Generates a 256-bit hardware-derived secret key using PBKDF2.
39
+ * Satisfies Claim 1 & 3: Hardware fingerprint key derivation.
40
+ */
41
+ function getHardwareDerivedSecret() {
42
+ const networkInterfaces = node_os_1.default.networkInterfaces();
43
+ const macAddresses = [];
44
+ for (const name of Object.keys(networkInterfaces)) {
45
+ const interfaces = networkInterfaces[name];
46
+ if (interfaces) {
47
+ for (const net of interfaces) {
48
+ if (!net.internal && net.mac && net.mac !== '00:00:00:00:00:00') {
49
+ macAddresses.push(net.mac);
50
+ }
51
+ }
52
+ }
53
+ }
54
+ const primaryMac = macAddresses.sort()[0] || 'anchor-default-mac';
55
+ const cpus = node_os_1.default.cpus();
56
+ const cpuModel = cpus.length > 0 ? cpus[0].model : 'generic-cpu';
57
+ const machineUuid = getSystemMachineUuid();
58
+ const rawFingerprint = [
59
+ machineUuid,
60
+ primaryMac,
61
+ cpuModel,
62
+ node_os_1.default.arch(),
63
+ node_os_1.default.platform(),
64
+ ].join('::');
65
+ // Derive key via PBKDF2 with 100,000 iterations for cryptographic strength
66
+ return node_crypto_1.default.pbkdf2Sync(rawFingerprint, 'anchorgit-hardware-salt-v1', 100000, 32, 'sha256');
67
+ }
68
+ /**
69
+ * Computes a workstation-bound HMAC-SHA256 signature with in-memory buffer purging.
70
+ * Satisfies Claim 1, 6 & 10: Zero-Knowledge memory processing & non-repudiable workstation signing.
71
+ */
72
+ function computeHmacSignature(payload) {
73
+ const hardwareSecretBuffer = getHardwareDerivedSecret();
74
+ const workstationGuid = hardwareSecretBuffer.toString('hex').substring(0, 32);
75
+ const derivedKeyString = node_crypto_1.default
76
+ .createHash('sha256')
77
+ .update(`${workstationGuid}::anchorgit-hardware-salt-v1`)
78
+ .digest('hex');
79
+ const canonicalString = [
80
+ payload.commit_sha,
81
+ payload.branch,
82
+ payload.lines_added,
83
+ payload.lines_deleted,
84
+ payload.diff_sha256,
85
+ payload.decision_summary,
86
+ payload.timestamp,
87
+ // Provenance tag and duration are part of the signed canonical string.
88
+ // Changing either field post-sign will invalidate the HMAC.
89
+ payload.deliberation_source,
90
+ payload.deliberation_seconds,
91
+ ].join('|');
92
+ const canonicalBuffer = Buffer.from(canonicalString, 'utf-8');
93
+ // Compute HMAC signature using hardware-bound secret string
94
+ const signature = node_crypto_1.default
95
+ .createHmac('sha256', derivedKeyString)
96
+ .update(canonicalBuffer)
97
+ .digest('hex');
98
+ // 🔒 MEMORY PURGING (Patent Claim 10 Requirement)
99
+ // Overwrite sensitive key and input buffers in volatile RAM immediately
100
+ hardwareSecretBuffer.fill(0);
101
+ canonicalBuffer.fill(0);
102
+ return signature;
103
+ }
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.isGitRepository = isGitRepository;
7
+ exports.getLocalGitStats = getLocalGitStats;
8
+ const node_child_process_1 = require("node:child_process");
9
+ const node_crypto_1 = __importDefault(require("node:crypto"));
10
+ const node_fs_1 = __importDefault(require("node:fs"));
11
+ function isGitRepository() {
12
+ try {
13
+ const res = (0, node_child_process_1.execSync)('git rev-parse --is-inside-work-tree', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] });
14
+ return res.trim() === 'true';
15
+ }
16
+ catch {
17
+ return false;
18
+ }
19
+ }
20
+ function getLocalGitStats() {
21
+ try {
22
+ // 1. Current Branch & Commit SHA
23
+ const branch = (0, node_child_process_1.execSync)('git rev-parse --abbrev-ref HEAD', { encoding: 'utf-8' }).trim();
24
+ const commitSha = (0, node_child_process_1.execSync)('git rev-parse HEAD', { encoding: 'utf-8' }).trim();
25
+ // 2. Untracked Files Detection
26
+ let untrackedFiles = [];
27
+ try {
28
+ const statusOutput = (0, node_child_process_1.execSync)('git status --porcelain', { encoding: 'utf-8' });
29
+ untrackedFiles = statusOutput
30
+ .split('\n')
31
+ .filter((line) => line.startsWith('??'))
32
+ .map((line) => line.substring(3).trim())
33
+ .filter(Boolean);
34
+ }
35
+ catch {
36
+ untrackedFiles = [];
37
+ }
38
+ // 3. Tracked Diff (staged + unstaged working tree changes)
39
+ let rawDiff = '';
40
+ try {
41
+ rawDiff = (0, node_child_process_1.execSync)('git diff HEAD', { encoding: 'utf-8' });
42
+ }
43
+ catch {
44
+ rawDiff = '';
45
+ }
46
+ // 4. Calculate untracked files content & line count
47
+ let untrackedContent = '';
48
+ let untrackedLinesAdded = 0;
49
+ for (const file of untrackedFiles) {
50
+ try {
51
+ if (node_fs_1.default.existsSync(file)) {
52
+ const content = node_fs_1.default.readFileSync(file, 'utf-8');
53
+ untrackedContent += `\n--- /dev/null\n+++ b/${file}\n` + content;
54
+ untrackedLinesAdded += content.split('\n').length;
55
+ }
56
+ }
57
+ catch {
58
+ // Skip unreadable files
59
+ }
60
+ }
61
+ const hasWorkingTreeChanges = rawDiff.trim().length > 0 || untrackedFiles.length > 0;
62
+ // Fallback: Inspect the latest commit (HEAD) directly if working tree is clean
63
+ if (!hasWorkingTreeChanges) {
64
+ try {
65
+ // Show diff for HEAD commit specifically
66
+ rawDiff = (0, node_child_process_1.execSync)('git show --format="" HEAD', { encoding: 'utf-8' });
67
+ }
68
+ catch {
69
+ rawDiff = '';
70
+ }
71
+ }
72
+ // 5. Compute LOC metrics
73
+ let linesAdded = untrackedLinesAdded;
74
+ let linesDeleted = 0;
75
+ const lines = rawDiff.split('\n');
76
+ for (const line of lines) {
77
+ if (line.startsWith('+') && !line.startsWith('+++')) {
78
+ linesAdded++;
79
+ }
80
+ else if (line.startsWith('-') && !line.startsWith('---')) {
81
+ linesDeleted++;
82
+ }
83
+ }
84
+ // 6. List of Affected Files (Scoped strictly to the latest commit HEAD)
85
+ let affectedFilesRaw = '';
86
+ try {
87
+ // Always inspect the exact files inside HEAD commit first
88
+ affectedFilesRaw = (0, node_child_process_1.execSync)('git show --name-only --format="" --diff-filter=ACMRT HEAD', { encoding: 'utf-8' });
89
+ }
90
+ catch {
91
+ affectedFilesRaw = '';
92
+ }
93
+ // If HEAD diff is empty (e.g., initial empty repo commit), fallback to working tree diff
94
+ if (!affectedFilesRaw.trim() && hasWorkingTreeChanges) {
95
+ try {
96
+ affectedFilesRaw = (0, node_child_process_1.execSync)('git diff --name-only', { encoding: 'utf-8' });
97
+ }
98
+ catch {
99
+ affectedFilesRaw = '';
100
+ }
101
+ }
102
+ const trackedAffectedFiles = affectedFilesRaw
103
+ .split('\n')
104
+ .map((f) => f.trim())
105
+ .filter((f) => f.length > 0)
106
+ .filter((f) => {
107
+ try {
108
+ return node_fs_1.default.existsSync(f) ? node_fs_1.default.statSync(f).isFile() : true;
109
+ }
110
+ catch {
111
+ return true;
112
+ }
113
+ });
114
+ const affectedFiles = Array.from(new Set(trackedAffectedFiles));
115
+ // 7. 🔒 MEMORY PURGING & CRYPTOGRAPHIC HASHING
116
+ const fullDiffText = rawDiff + untrackedContent || `${commitSha}:${branch}:${affectedFiles.join(',')}`;
117
+ const diffBuffer = Buffer.from(fullDiffText, 'utf-8');
118
+ // SHA-256 Checksum over state
119
+ const diffHash = node_crypto_1.default.createHash('sha256').update(diffBuffer).digest('hex');
120
+ // PURGE VOLATILE MEMORY (Patent Claim Requirement)
121
+ diffBuffer.fill(0);
122
+ return {
123
+ commitSha,
124
+ branch,
125
+ linesAdded,
126
+ linesDeleted,
127
+ diffHash,
128
+ affectedFiles,
129
+ };
130
+ }
131
+ catch (err) {
132
+ throw new Error(`Failed to read Git stats: ${err.message}`);
133
+ }
134
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/dist/index.js ADDED
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const commander_1 = require("commander");
4
+ const decide_js_1 = require("./commands/decide.js");
5
+ const pair_js_1 = require("./commands/pair.js");
6
+ const standup_js_1 = require("./commands/standup.js");
7
+ const context_js_1 = require("./commands/context.js");
8
+ const pr_js_1 = require("./commands/pr.js");
9
+ const whoami_js_1 = require("./commands/whoami.js");
10
+ const log_js_1 = require("./commands/log.js");
11
+ const program = new commander_1.Command();
12
+ program
13
+ .name('anchor')
14
+ .description('AnchorGit - Zero-Knowledge Decision Ledger for Developers (Open Source)')
15
+ .version('0.1.0')
16
+ .addHelpText('after', `
17
+
18
+ 💡 How & When to Use AnchorGit in your Daily Workflow:
19
+
20
+ 1. Log a trade-off or decision before committing/pushing:
21
+ $ anchor decide "Switched session storage to Redis for scaling; accepted ~2ms network latency penalty."
22
+
23
+ 2. Verify zero-knowledge security payload (no source code is sent):
24
+ $ anchor decide "Refactored payment gateway retry logic" --dry-run
25
+
26
+ 3. Generate your morning standup summary from recent decision logs:
27
+ $ anchor standup
28
+
29
+ 4. Pair with your team workspace on anchorgit.com:
30
+ $ anchor pair <your-api-key>
31
+ `);
32
+ program
33
+ .command('pair')
34
+ .description('Link this workstation with your AnchorGit team dashboard using an API key')
35
+ .argument('<token>', 'API key or pairing token from anchorgit.com')
36
+ .action((token) => {
37
+ (0, pair_js_1.handlePair)(token);
38
+ });
39
+ program
40
+ .command('decide')
41
+ .description('Record the WHY (architectural trade-offs, design choices, or AI steering) for your current code changes')
42
+ .argument('<message>', 'Brief summary of why you made this change or what trade-off was accepted')
43
+ .option('--dry-run', 'Print the exact zero-knowledge JSON payload to stdout without sending anywhere')
44
+ .option('-c, --category <type>', 'Category of decision (e.g. architecture, ai-steering, security)', 'architecture')
45
+ .option('-d, --deliberation <seconds>', 'Deliberation focus time in seconds', '0')
46
+ .addOption(
47
+ // Hidden flag consumed by the VS Code extension.
48
+ // Allows the extension to attest that deliberation_seconds was recorded
49
+ // passively by the IDE timer, rather than manually declared by the user.
50
+ // Not shown in --help to keep the user-facing CLI surface clean.
51
+ new commander_1.Option('--source <source>', 'Provenance source for deliberation time (set by the VS Code extension)').hideHelp())
52
+ .addHelpText('after', `
53
+ Examples:
54
+ $ anchor decide "Switched session store to Redis cluster for multi-region scale"
55
+ $ anchor decide "Overrode LLM recommendation to use raw SQL for 10x throughput"
56
+
57
+ 💡 When to Notarize:
58
+ • DO: Architectural trade-offs, AI course corrections, security shifts, breaking schema changes.
59
+ • SKIP: Typo fixes, CSS tweaks, linting, or routine dependency updates.
60
+ `)
61
+ .action((message, options) => {
62
+ (0, decide_js_1.handleDecide)(message, options);
63
+ });
64
+ program
65
+ .command('context [path]')
66
+ .description('Query historical decision context for a specific file or path')
67
+ .action((message) => {
68
+ (0, context_js_1.handleContext)(message);
69
+ });
70
+ program
71
+ .command('pr')
72
+ .description('Generate an intent-first Pull Request Decision Brief for code reviewers')
73
+ .action(() => {
74
+ (0, pr_js_1.handlePr)();
75
+ });
76
+ program
77
+ .command('standup')
78
+ .description('Generate a 24-hour engineering impact summary from signed decision logs')
79
+ .action(() => {
80
+ (0, standup_js_1.handleStandup)();
81
+ });
82
+ program
83
+ .command('log')
84
+ .description('Display formatted decision ledger history')
85
+ .action(() => {
86
+ (0, log_js_1.handleLog)();
87
+ });
88
+ program
89
+ .command('whoami')
90
+ .description('Display currently paired developer identity and public profile link')
91
+ .action(whoami_js_1.handleWhoami);
92
+ program.parse(process.argv);
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "anchorgit-cli",
3
+ "version": "0.1.0",
4
+ "description": "Cryptographic decision logging and human steering for Git.",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "anchor": "dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc",
14
+ "prepublishOnly": "npm run build"
15
+ },
16
+ "homepage": "https://anchorgit.com",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/rahool-bk/anchorgit-cli.git"
20
+ },
21
+ "keywords": [
22
+ "git",
23
+ "cli",
24
+ "developer-tools",
25
+ "ai-governance",
26
+ "architecture-decision-records"
27
+ ],
28
+ "license": "Apache-2.0",
29
+ "dependencies": {
30
+ "commander": "^12.1.0"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^20.0.0",
34
+ "typescript": "^5.0.0"
35
+ }
36
+ }