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 +201 -0
- package/README.md +137 -0
- package/dist/api/client.js +40 -0
- package/dist/commands/context.js +60 -0
- package/dist/commands/decide.js +122 -0
- package/dist/commands/log.js +44 -0
- package/dist/commands/pair.js +62 -0
- package/dist/commands/pr.js +74 -0
- package/dist/commands/standup.js +42 -0
- package/dist/commands/whoami.js +19 -0
- package/dist/core/config.js +64 -0
- package/dist/core/crypto.js +103 -0
- package/dist/core/git.js +134 -0
- package/dist/core/sanitize.js +2 -0
- package/dist/index.js +92 -0
- package/package.json +36 -0
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
|
+
}
|
package/dist/core/git.js
ADDED
|
@@ -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
|
+
}
|
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
|
+
}
|