@cassiomc1/forgeloop 1.10.1 → 1.11.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/.cursor/rules/project-loop.mdc +2 -2
- package/.forgeloop/forgeloop.gitignore +1 -0
- package/.github/copilot-instructions.md +2 -2
- package/AGENTS.md +1 -0
- package/AGENT_COMPATIBILITY.md +7 -0
- package/CLAUDE.md +1 -0
- package/DOCS_INDEX.md +13 -1
- package/LOOP_SYSTEM_DESIGN.md +12 -0
- package/ORCHESTRATOR_INTEGRATION.md +9 -0
- package/PROTOCOL_INTEGRATION.md +17 -0
- package/README.md +21 -18
- package/THIRD_PARTY_NOTICES.md +11 -0
- package/THREAT_MODEL.md +21 -0
- package/benchmarks/repository-index/README.md +73 -0
- package/benchmarks/repository-index/queries.json +12 -0
- package/benchmarks/repository-index/run-hot-path.mjs +142 -0
- package/benchmarks/repository-index/run-persistent-transport.mjs +169 -0
- package/completions/_forgeloop +7 -1
- package/completions/forgeloop.bash +13 -1
- package/completions/forgeloop.fish +40 -1
- package/docs/ADVISORY_CONTEXT.md +24 -0
- package/docs/AGENT_PROTOCOL_SUMMARY.md +8 -1
- package/docs/CLI_REFERENCE.md +114 -2
- package/docs/DOCUMENTATION_GUIDE.md +10 -5
- package/docs/GETTING_STARTED.md +17 -0
- package/docs/MCP.md +16 -1
- package/docs/PACKAGE_CONTENTS.md +12 -1
- package/docs/PERSISTENT_SEARCH_TRANSPORT.md +289 -0
- package/docs/RECIPES.md +30 -0
- package/docs/RELEASE_CHECKLIST.md +36 -2
- package/docs/REPOSITORY_INDEX.md +553 -0
- package/docs/RIPWIRE_ADAPTER.md +189 -0
- package/docs/TROUBLESHOOTING.md +147 -0
- package/docs/UNIVERSAL_INTEGRATION.md +30 -0
- package/docs/diagrams/README.md +19 -0
- package/package.json +12 -2
- package/scripts/update-tgrep-manifest.mjs +86 -0
- package/scripts/verify-tgrep-manifest.mjs +17 -0
- package/src/adapters/ripwire/normalize.js +352 -0
- package/src/adapters/ripwire/process.js +248 -0
- package/src/adapters/ripwire/provider.js +245 -0
- package/src/cli.js +35 -0
- package/src/commands/doctor.js +76 -1
- package/src/commands/index-rebuild.js +1 -0
- package/src/commands/index-setup.js +1 -0
- package/src/commands/index-start.js +1 -0
- package/src/commands/index-status.js +1 -0
- package/src/commands/index-stop.js +1 -0
- package/src/commands/init.js +39 -1
- package/src/commands/repository-index.js +111 -0
- package/src/commands/search.js +1 -0
- package/src/commands/update.js +32 -4
- package/src/core/advisory-context/service.js +36 -14
- package/src/core/cli-command-definitions.js +97 -3
- package/src/core/command-executors.js +35 -2
- package/src/core/command-input.js +23 -0
- package/src/core/error-codes.js +195 -0
- package/src/core/filesystem.js +10 -1
- package/src/core/handoff-acceptance.js +10 -1
- package/src/core/integration-invocation-policy.js +27 -0
- package/src/core/integration-resources.js +16 -1
- package/src/core/protocol-info.js +23 -0
- package/src/core/work-state.js +15 -6
- package/src/integration.d.ts +101 -0
- package/src/integration.js +22 -0
- package/src/persistent-transport/client.js +293 -0
- package/src/persistent-transport/constants.js +24 -0
- package/src/persistent-transport/errors.js +38 -0
- package/src/persistent-transport/framing.js +61 -0
- package/src/persistent-transport/lifecycle.js +116 -0
- package/src/persistent-transport/ownership.js +184 -0
- package/src/persistent-transport/paths.js +31 -0
- package/src/persistent-transport/protocol.js +95 -0
- package/src/persistent-transport/server.js +256 -0
- package/src/persistent-transport/state.js +49 -0
- package/src/repository-index/args.js +59 -0
- package/src/repository-index/binary-manager.js +413 -0
- package/src/repository-index/constants.js +45 -0
- package/src/repository-index/errors.js +38 -0
- package/src/repository-index/lifecycle.js +17 -0
- package/src/repository-index/lock.js +113 -0
- package/src/repository-index/manifest.js +132 -0
- package/src/repository-index/metrics.js +30 -0
- package/src/repository-index/normalize-json.js +187 -0
- package/src/repository-index/paths.js +39 -0
- package/src/repository-index/platform.js +20 -0
- package/src/repository-index/process.js +140 -0
- package/src/repository-index/readiness.js +62 -0
- package/src/repository-index/search.js +262 -0
- package/src/repository-index/server.js +432 -0
- package/src/repository-index/status.js +397 -0
- package/src/repository-index/tgrep-manifest.json +38 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# Ripwire advisory adapter
|
|
2
|
+
|
|
3
|
+
This document explains the optional Ripwire integration shipped with
|
|
4
|
+
ForgeLoop. It is an advisory context provider. It can help a host choose
|
|
5
|
+
which source files to inspect, but it never controls ForgeLoop state, checks,
|
|
6
|
+
receipts, commands, approvals, or completion.
|
|
7
|
+
|
|
8
|
+
## When to use it
|
|
9
|
+
|
|
10
|
+
Use the adapter when a host already has a qualified Ripwire executable and
|
|
11
|
+
wants ranked source signatures for an explicit task query. Do not use it as a
|
|
12
|
+
replacement for tests, code review, lifecycle evidence, or a sound call graph.
|
|
13
|
+
Ripwire's resolver is approximate: same-file and same-directory edges are
|
|
14
|
+
useful hints, while ambiguous or unresolved edges can be missing or wrong.
|
|
15
|
+
|
|
16
|
+
ForgeLoop does not install Ripwire, search `PATH`, contact a server, create a
|
|
17
|
+
cache, or persist a recall. The host owns executable selection and version
|
|
18
|
+
qualification.
|
|
19
|
+
|
|
20
|
+
## Registration
|
|
21
|
+
|
|
22
|
+
Import the factory from the public integration entry point and register the
|
|
23
|
+
returned provider under the exact `ripwire` key:
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
import {
|
|
27
|
+
createForgeLoopContext,
|
|
28
|
+
createRipwireAdvisoryContextProvider,
|
|
29
|
+
recallAdvisoryContext,
|
|
30
|
+
} from "@cassiomc1/forgeloop/integration";
|
|
31
|
+
|
|
32
|
+
const ripwire = createRipwireAdvisoryContextProvider({
|
|
33
|
+
executablePath: "/absolute/path/to/ripwire",
|
|
34
|
+
expectedVersion: "0.3.8",
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const runtimeContext = createForgeLoopContext({
|
|
38
|
+
advisoryContextProviders: { ripwire },
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const context = await recallAdvisoryContext({
|
|
42
|
+
target: "/absolute/path/to/project",
|
|
43
|
+
taskId: "task-123",
|
|
44
|
+
providerName: "ripwire",
|
|
45
|
+
query: "stale handoff acceptance and repository fingerprint",
|
|
46
|
+
limit: 6,
|
|
47
|
+
runtimeContext,
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`executablePath` must be absolute. `expectedVersion` is an exact version token,
|
|
52
|
+
not a range. Provider construction is inert. On every recall the adapter first
|
|
53
|
+
runs `ripwire --version`; a mismatch fails with
|
|
54
|
+
`E_ADVISORY_CONTEXT_PROVIDER_INVALID` before the query is attempted.
|
|
55
|
+
|
|
56
|
+
## Process contract
|
|
57
|
+
|
|
58
|
+
The adapter invokes one command with an argv array:
|
|
59
|
+
|
|
60
|
+
```text
|
|
61
|
+
<ripwire-path> <absolute-project-path> \
|
|
62
|
+
--for=<entire-query-string> --signatures-only --json --no-cache \
|
|
63
|
+
--exclude=.forgeloop
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The query is one argument, so shell metacharacters cannot add arguments or
|
|
67
|
+
commands. The child is started with `shell: false`, standard input is closed,
|
|
68
|
+
and stdout/stderr are read concurrently. A single deadline covers the version
|
|
69
|
+
probe and query. The default transport ceilings are 1 MiB for stdout and 64
|
|
70
|
+
KiB for stderr. On timeout or overflow the child is terminated and the error
|
|
71
|
+
uses a stable ForgeLoop code; raw output is not copied into the message.
|
|
72
|
+
|
|
73
|
+
The `.forgeloop` exclusion keeps lifecycle files out of the advisory source
|
|
74
|
+
surface. The adapter does not add other exclusion flags because every flag
|
|
75
|
+
must be qualified against the selected Ripwire version.
|
|
76
|
+
|
|
77
|
+
## JSON mapping
|
|
78
|
+
|
|
79
|
+
Ripwire's `--for --json` response is expected to be an object containing a
|
|
80
|
+
flat `sigs` array. Each known row is mapped as follows:
|
|
81
|
+
|
|
82
|
+
| Ripwire field | ForgeLoop field | Rule |
|
|
83
|
+
| --- | --- | --- |
|
|
84
|
+
| `n` | `title` | Candidate symbol name. |
|
|
85
|
+
| `sig` | `summary` | Signature text, bounded before core normalization. |
|
|
86
|
+
| `p` + `l` | `sourceRef` | Repository-relative path and one-based line. |
|
|
87
|
+
| `r`, `k` | summary annotation | Rank and ranking score remain descriptive text. |
|
|
88
|
+
| numeric `confidence` in `[0, 1]` | `confidence` | Copied only when the upstream field is explicitly numeric and bounded. |
|
|
89
|
+
| `at` | omitted | A run timestamp or revision is not needed for deterministic item identity. |
|
|
90
|
+
|
|
91
|
+
PageRank, BM25, margin, and other ranking values are never converted into a
|
|
92
|
+
probability. Unknown fields are discarded by the core allowlist. Candidate
|
|
93
|
+
order is preserved, duplicates are removed by stable first occurrence, and
|
|
94
|
+
items stop when the requested item or total-character budget is reached.
|
|
95
|
+
|
|
96
|
+
The first item is always `Ripwire advisory status`. It states that the result
|
|
97
|
+
is approximate and carries bounded disclosures such as `capped`, `sigs_total`,
|
|
98
|
+
`sigs_shown`, `lens`, `ambiguous`, `unresolved`, `unindexed`, parse health, and
|
|
99
|
+
`index_completeness=unknown`. If no symbol fits, the status item says so; an
|
|
100
|
+
empty result never proves that the project has no impact.
|
|
101
|
+
|
|
102
|
+
Source references are rejected when they are absolute outside the project,
|
|
103
|
+
contain traversal segments, use an in-project symlink, or report an invalid
|
|
104
|
+
line. The host must still inspect the referenced file and independently verify
|
|
105
|
+
the proposed change.
|
|
106
|
+
|
|
107
|
+
The status item is budget-aware. When candidates or diagnostic notices do not
|
|
108
|
+
fit, it preserves a truthful completeness warning and says which candidate
|
|
109
|
+
rows or text were omitted. The final item total is checked against the same
|
|
110
|
+
character budget used by the core advisory normalizer.
|
|
111
|
+
|
|
112
|
+
## Failure codes
|
|
113
|
+
|
|
114
|
+
| Situation | Code |
|
|
115
|
+
| --- | --- |
|
|
116
|
+
| Missing or unqualified executable or unsafe target | `E_ADVISORY_CONTEXT_PROVIDER_UNAVAILABLE` |
|
|
117
|
+
| Nonzero process exit without a qualified meaning | `E_ADVISORY_CONTEXT_RESULT_INVALID` |
|
|
118
|
+
| Expected version differs from `--version` output | `E_ADVISORY_CONTEXT_PROVIDER_INVALID` |
|
|
119
|
+
| Invalid JSON or unsupported response shape | `E_ADVISORY_CONTEXT_RESULT_INVALID` |
|
|
120
|
+
| Timeout | `E_ADVISORY_CONTEXT_TIMEOUT` |
|
|
121
|
+
| Stdout/stderr or candidate ceiling exceeded | `E_ADVISORY_CONTEXT_OUTPUT_LIMIT` |
|
|
122
|
+
| Unsafe content or control character in selected context | `E_PORTABLE_CONTEXT_INVALID` |
|
|
123
|
+
|
|
124
|
+
These failures affect the explicit recall operation only. They do not change a
|
|
125
|
+
ForgeLoop task phase and do not write `.forgeloop` state.
|
|
126
|
+
|
|
127
|
+
## Verification
|
|
128
|
+
|
|
129
|
+
The deterministic fixture tests run without a Ripwire installation:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
node --test \
|
|
133
|
+
tests/ripwire-advisory-process.test.js \
|
|
134
|
+
tests/ripwire-advisory-normalize.test.js \
|
|
135
|
+
tests/ripwire-advisory-provider.test.js
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The real-binary smoke test is opt-in. Set both variables to a host-qualified
|
|
139
|
+
binary and version, then run:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
FORGELOOP_TEST_RIPWIRE_PATH=/absolute/path/to/ripwire \
|
|
143
|
+
FORGELOOP_TEST_RIPWIRE_VERSION=0.3.8 \
|
|
144
|
+
node --test tests/real-ripwire-advisory.test.js
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Without those variables the test is skipped and interoperability remains
|
|
148
|
+
`NOT_VERIFIED`. When it runs, it requires a candidate reference to the known
|
|
149
|
+
fixture source and compares the complete fixture tree before and after recall,
|
|
150
|
+
so a binary that mutates the project fails the test. The test does not install
|
|
151
|
+
software or discover a binary.
|
|
152
|
+
|
|
153
|
+
## Retrieval benchmark
|
|
154
|
+
|
|
155
|
+
The retrieval benchmark is a repository-maintainer check. Its runner, cases,
|
|
156
|
+
and fixture corpus are intentionally excluded from the core npm tarball; the
|
|
157
|
+
published consumer surface is the adapter, its declarations, and this guide.
|
|
158
|
+
Run it from a clean ForgeLoop checkout when a host-qualified Ripwire binary is
|
|
159
|
+
available.
|
|
160
|
+
|
|
161
|
+
`benchmarks/ripwire-context/cases.json` freezes six task-shaped queries,
|
|
162
|
+
expected files, lexical baseline terms, and the ForgeLoop commit used for the
|
|
163
|
+
comparison. Run the benchmark only against a clean checkout and an explicitly
|
|
164
|
+
qualified binary:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
node scripts/benchmark-ripwire-context.mjs \
|
|
168
|
+
--project /absolute/path/to/forgeloop \
|
|
169
|
+
--ripwire-path /absolute/path/to/ripwire \
|
|
170
|
+
--version 0.3.8 \
|
|
171
|
+
--cases benchmarks/ripwire-context/cases.json \
|
|
172
|
+
--runs 5 \
|
|
173
|
+
--json
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
The report includes per-case and per-run expected-file coverage, misses,
|
|
177
|
+
irrelevant references, baseline bytes, normalized adapter bytes, transport
|
|
178
|
+
bytes when observed, and median/min/max durations. A file seen in only one
|
|
179
|
+
repetition is reported as observed across runs but does not count as a
|
|
180
|
+
consistently found file. A missing binary, version, dirty checkout, or commit
|
|
181
|
+
mismatch produces `NOT_VERIFIED`; it is never reported as a performance win.
|
|
182
|
+
|
|
183
|
+
## Scope boundary
|
|
184
|
+
|
|
185
|
+
The adapter is intentionally limited to source-map retrieval. It does not
|
|
186
|
+
implement Ripwire's body packing, impact commands, cache management, server
|
|
187
|
+
mode, automatic file reads, lifecycle transitions, evidence production, or
|
|
188
|
+
publication. Those features require a separate contract and separate
|
|
189
|
+
qualification work.
|
package/docs/TROUBLESHOOTING.md
CHANGED
|
@@ -33,6 +33,10 @@ This guide provides symptom-first recovery procedures for common ForgeLoop proto
|
|
|
33
33
|
- [Structural-quality verification reports a regression](#symptom-structural-quality-verification-reports-a-regression)
|
|
34
34
|
- [Structural-quality provider is unavailable or invalid](#symptom-structural-quality-provider-is-unavailable-or-invalid)
|
|
35
35
|
- [Structural-quality evidence is stale or incomparable](#symptom-structural-quality-evidence-is-stale-or-incomparable)
|
|
36
|
+
- [Repository Index is not ready](#symptom-repository-index-is-not-ready)
|
|
37
|
+
- [Repository search fails](#symptom-repository-search-fails)
|
|
38
|
+
- [Persistent search host is unavailable](#symptom-persistent-search-host-is-unavailable)
|
|
39
|
+
- [Persistent search host ownership is unverified or stale](#symptom-persistent-search-host-ownership-is-unverified-or-stale)
|
|
36
40
|
- [Another harness cannot resume the task](#symptom-another-harness-cannot-resume)
|
|
37
41
|
- [Task claim conflict or recovered task](#symptom-task-creation-blocked-by-a-write-claim-conflict-e_task_scope_conflict)
|
|
38
42
|
- [Stable Error & Reason Code Reference](#stable-error-and-reason-codes)
|
|
@@ -1016,6 +1020,120 @@ Resolve the legitimate drift through the normal task lifecycle. Do not edit a
|
|
|
1016
1020
|
baseline or evaluation by hand, and do not use a bundle as a reason to bypass
|
|
1017
1021
|
current-cycle validation.
|
|
1018
1022
|
|
|
1023
|
+
### Symptom: Repository Index is not ready
|
|
1024
|
+
|
|
1025
|
+
#### What it means
|
|
1026
|
+
|
|
1027
|
+
`forgeloop index-status` did not report `READY`. For a Git repository the
|
|
1028
|
+
engine is mandatory, so `doctor` and repository-wide search remain unhealthy
|
|
1029
|
+
until the pinned binary, derived index, and ForgeLoop-owned watcher are
|
|
1030
|
+
verified.
|
|
1031
|
+
|
|
1032
|
+
#### Inspect
|
|
1033
|
+
|
|
1034
|
+
```bash
|
|
1035
|
+
forgeloop index-status --json
|
|
1036
|
+
forgeloop doctor --json
|
|
1037
|
+
```
|
|
1038
|
+
|
|
1039
|
+
#### Safe recovery
|
|
1040
|
+
|
|
1041
|
+
1. Run `forgeloop index-start --json` when the index is complete but the
|
|
1042
|
+
watcher is down.
|
|
1043
|
+
2. Run `forgeloop index-setup --asset /absolute/path/to/pinned-asset.tar.gz`
|
|
1044
|
+
for a missing engine or an air-gapped host.
|
|
1045
|
+
3. Run `forgeloop index-rebuild --json` after corruption or a confirmed stale
|
|
1046
|
+
index.
|
|
1047
|
+
|
|
1048
|
+
Never delete the whole `.forgeloop` directory and never stop a process by
|
|
1049
|
+
executable name. A status of `DEFERRED` for a non-Git protocol fixture is
|
|
1050
|
+
intentional and is not evidence that a real repository is healthy.
|
|
1051
|
+
|
|
1052
|
+
### Symptom: Repository search fails
|
|
1053
|
+
|
|
1054
|
+
#### What it means
|
|
1055
|
+
|
|
1056
|
+
The provider-neutral search service rejected the request, could not verify the
|
|
1057
|
+
managed engine, or received an invalid native result. Search never falls back
|
|
1058
|
+
silently to `rg`, `grep`, or an arbitrary `PATH` executable.
|
|
1059
|
+
|
|
1060
|
+
#### Inspect
|
|
1061
|
+
|
|
1062
|
+
```bash
|
|
1063
|
+
forgeloop search "pattern" --json
|
|
1064
|
+
forgeloop index-status --json
|
|
1065
|
+
```
|
|
1066
|
+
|
|
1067
|
+
Correct request bounds such as the pattern, filters, context, or `max-count`
|
|
1068
|
+
when the error is `E_REPOSITORY_INDEX_REQUEST_INVALID`. For
|
|
1069
|
+
`E_REPOSITORY_INDEX_OUTPUT_INVALID`, preserve the bounded diagnostic and
|
|
1070
|
+
rebuild; repeated malformed output indicates a pinned-engine regression.
|
|
1071
|
+
Native exit code `1` means no matches and is normalized to a successful empty
|
|
1072
|
+
ForgeLoop result. Native exit code `2` is an actual search failure.
|
|
1073
|
+
|
|
1074
|
+
### Symptom: Persistent search host is unavailable
|
|
1075
|
+
|
|
1076
|
+
#### What it means
|
|
1077
|
+
|
|
1078
|
+
The CLI-only local transport could not connect to or start its user-scoped
|
|
1079
|
+
ForgeLoop host within the bounded startup/request timeout. This is a transport
|
|
1080
|
+
failure, not permission to bypass the mandatory Repository Index or switch to
|
|
1081
|
+
`rg`, `grep`, or an arbitrary executable.
|
|
1082
|
+
|
|
1083
|
+
#### Inspect
|
|
1084
|
+
|
|
1085
|
+
```bash
|
|
1086
|
+
forgeloop doctor --json
|
|
1087
|
+
forgeloop index-status --json
|
|
1088
|
+
forgeloop search "pattern" --json
|
|
1089
|
+
```
|
|
1090
|
+
|
|
1091
|
+
#### Safe recovery
|
|
1092
|
+
|
|
1093
|
+
Retry the search once after confirming that the current package is complete
|
|
1094
|
+
and the managed Repository Index reports `READY`. The client performs at most
|
|
1095
|
+
one bounded transport recovery attempt. If it still fails, preserve the
|
|
1096
|
+
structured error and follow the Repository Index recovery procedure above;
|
|
1097
|
+
transport recovery does not rebuild task state or completion evidence.
|
|
1098
|
+
|
|
1099
|
+
`E_PERSISTENT_TRANSPORT_UNAVAILABLE`,
|
|
1100
|
+
`E_PERSISTENT_TRANSPORT_TIMEOUT`, and
|
|
1101
|
+
`E_PERSISTENT_TRANSPORT_START_FAILED` identify the local transport boundary.
|
|
1102
|
+
The canonical search error, if one is returned after a successful connection,
|
|
1103
|
+
must be diagnosed as a Repository Index or request error instead.
|
|
1104
|
+
|
|
1105
|
+
### Symptom: Persistent search host ownership is unverified or stale
|
|
1106
|
+
|
|
1107
|
+
#### What it means
|
|
1108
|
+
|
|
1109
|
+
The local state or endpoint does not prove that the live process is the expected
|
|
1110
|
+
ForgeLoop persistent host. Common causes include a host from another package
|
|
1111
|
+
version, a process that exited while state remained, endpoint substitution, or
|
|
1112
|
+
PID reuse. A PID or process name alone is never sufficient ownership evidence.
|
|
1113
|
+
|
|
1114
|
+
#### Inspect
|
|
1115
|
+
|
|
1116
|
+
```bash
|
|
1117
|
+
forgeloop doctor --json
|
|
1118
|
+
forgeloop search "pattern" --json
|
|
1119
|
+
```
|
|
1120
|
+
|
|
1121
|
+
The sanitized `persistentTransport` status reports only its public schema,
|
|
1122
|
+
state, running/owned flags, and protocol/package versions. It intentionally
|
|
1123
|
+
does not print endpoint, home, lock, repository, index, binary, or entrypoint
|
|
1124
|
+
paths.
|
|
1125
|
+
|
|
1126
|
+
#### Safe recovery
|
|
1127
|
+
|
|
1128
|
+
Allow the CLI's bounded recovery path to reconcile a stale or incompatible
|
|
1129
|
+
transport state. Explicit shutdown also requires a verified endpoint and nonce.
|
|
1130
|
+
If the status remains `OWNERSHIP_UNVERIFIED`, do not kill a PID or delete a
|
|
1131
|
+
whole `.forgeloop` directory; inspect the structured error and use the normal
|
|
1132
|
+
package/process recovery boundary. The relevant stable codes are
|
|
1133
|
+
`E_PERSISTENT_TRANSPORT_OWNERSHIP_UNVERIFIED`,
|
|
1134
|
+
`E_PERSISTENT_TRANSPORT_HOST_STALE`, and
|
|
1135
|
+
`E_PERSISTENT_TRANSPORT_PROTOCOL_MISMATCH`.
|
|
1136
|
+
|
|
1019
1137
|
## Stable Error and Reason Codes
|
|
1020
1138
|
|
|
1021
1139
|
<!-- BEGIN FORGELOOP GENERATED: public-error-codes -->
|
|
@@ -1162,6 +1280,16 @@ current-cycle validation.
|
|
|
1162
1280
|
| `E_NATIVE_ADAPTER_TARGET_MISSING` | A ForgeLoop protocol validation or lifecycle condition was not satisfied. | Inspect the structured command result, correct the named artifact or prerequisite, then run forgeloop next --json. |
|
|
1163
1281
|
| `E_NEW_POLICY_VIOLATION` | New executable policy violation detected that is not present in brownfield baseline. | Fix the violation before completing the task. |
|
|
1164
1282
|
| `E_OBSERVATION_INVALID` | A ForgeLoop protocol validation or lifecycle condition was not satisfied. | Inspect the structured command result, correct the named artifact or prerequisite, then run forgeloop next --json. |
|
|
1283
|
+
| `E_PERSISTENT_TRANSPORT_FRAME_INVALID` | The local persistent-search byte stream did not contain a complete valid JSON frame. | Retry the bounded local request; inspect the host only if malformed frames recur. |
|
|
1284
|
+
| `E_PERSISTENT_TRANSPORT_FRAME_TOO_LARGE` | A persistent-search request or response exceeded its bounded frame limit. | Narrow the search request or inspect the host resource boundary; oversized frames are rejected. |
|
|
1285
|
+
| `E_PERSISTENT_TRANSPORT_HOST_STALE` | Persistent-search state points to a host process that is no longer running. | Retry the CLI search so ForgeLoop can remove only the verified stale host state and restart it. |
|
|
1286
|
+
| `E_PERSISTENT_TRANSPORT_INVALID_REQUEST` | A local persistent-search request is outside the versioned transport contract. | Use the supported ForgeLoop search command or update the compatible client and host together. |
|
|
1287
|
+
| `E_PERSISTENT_TRANSPORT_INVALID_RESPONSE` | The persistent-search host returned a response outside the versioned transport contract. | Retry once through the bounded recovery path; do not consume an unvalidated response. |
|
|
1288
|
+
| `E_PERSISTENT_TRANSPORT_OWNERSHIP_UNVERIFIED` | ForgeLoop could not prove that the process or endpoint belongs to its user-scoped persistent-search host. | Do not terminate the process; inspect the endpoint and retry after resolving the ownership conflict. |
|
|
1289
|
+
| `E_PERSISTENT_TRANSPORT_PROTOCOL_MISMATCH` | The persistent-search client and host do not agree on the supported protocol version. | ForgeLoop may replace only a verified compatible host; otherwise update the installed package and retry. |
|
|
1290
|
+
| `E_PERSISTENT_TRANSPORT_START_FAILED` | The user-scoped persistent-search host could not start or become ready within its bounded startup window. | Inspect the structured diagnostics and retry; direct integration APIs remain available without this optimization. |
|
|
1291
|
+
| `E_PERSISTENT_TRANSPORT_TIMEOUT` | A persistent-search connection, handshake, or request exceeded its bounded timeout. | Retry once through the ownership-checked recovery path and inspect host/index health if it persists. |
|
|
1292
|
+
| `E_PERSISTENT_TRANSPORT_UNAVAILABLE` | The user-scoped persistent-search endpoint was not reachable. | ForgeLoop starts one verified local host and retries once; persistent failure is reported without an rg fallback. |
|
|
1165
1293
|
| `E_PHASE_CHRONOLOGY_INVALID` | A ForgeLoop protocol validation or lifecycle condition was not satisfied. | Inspect the structured command result, correct the named artifact or prerequisite, then run forgeloop next --json. |
|
|
1166
1294
|
| `E_PHASE_PREREQUISITE_MISSING` | A ForgeLoop protocol validation or lifecycle condition was not satisfied. | Inspect the structured command result, correct the named artifact or prerequisite, then run forgeloop next --json. |
|
|
1167
1295
|
| `E_PHASE_TRANSITION_INVALID` | A ForgeLoop protocol validation or lifecycle condition was not satisfied. | Inspect the structured command result, correct the named artifact or prerequisite, then run forgeloop next --json. |
|
|
@@ -1208,6 +1336,25 @@ current-cycle validation.
|
|
|
1208
1336
|
| `E_RECONCILE_REQUIREMENT_UNKNOWN` | The supplied check id and requirement text do not exactly match a contract verification item of type VERIFICATION. | Supply the exact id and requirement text of an existing contract verification item. |
|
|
1209
1337
|
| `E_RECONCILE_UNSUPPORTED_DRIFT` | Work-state drift includes kinds other than REPOSITORY_CHANGED (contract or required-artifact drift). | Resolve contract or artifact drift through their dedicated recovery surfaces; reconcile-closure only refreshes repository fingerprint drift. |
|
|
1210
1338
|
| `E_REPOSITORY_CHANGED` | The repository fingerprint (branch or HEAD) moved after the work-state checkpoint was recorded. | If the task objective is already satisfied in the current repository, run forgeloop reconcile-closure; otherwise resume from a checkpoint that matches the current repository. |
|
|
1339
|
+
| `E_REPOSITORY_INDEX_ENGINE_BINARY_CHECKSUM_MISMATCH` | The extracted or managed tgrep executable bytes do not match the pinned binary SHA-256 digest. | Run forgeloop index-setup or index-rebuild to repair the managed binary; do not run a mismatched executable. |
|
|
1340
|
+
| `E_REPOSITORY_INDEX_ENGINE_CHECKSUM_MISMATCH` | The tgrep archive or executable bytes do not match the pinned manifest SHA-256 digest. | Obtain the exact manifest asset and retry; do not install a checksum mismatch. |
|
|
1341
|
+
| `E_REPOSITORY_INDEX_ENGINE_DOWNLOAD_FAILED` | The pinned tgrep release asset could not be downloaded or read. | Retry with network access or preload the exact manifest archive; never bypass provisioning verification. |
|
|
1342
|
+
| `E_REPOSITORY_INDEX_ENGINE_EXECUTION_FAILED` | A managed tgrep process could not be launched, completed, or stayed within its execution boundary. | Inspect the structured error and index status, then retry with the verified managed engine. |
|
|
1343
|
+
| `E_REPOSITORY_INDEX_ENGINE_EXTRACTION_FAILED` | The tgrep archive is malformed, unsafe, or could not be extracted to a temporary directory. | Use the exact supported archive and retry; unsafe paths and links are rejected. |
|
|
1344
|
+
| `E_REPOSITORY_INDEX_ENGINE_MISSING` | The managed tgrep executable is absent, unreadable, or has not been provisioned. | Run forgeloop index-setup or preload the exact manifest archive with --asset. |
|
|
1345
|
+
| `E_REPOSITORY_INDEX_ENGINE_VERSION_MISMATCH` | The executable reports a version different from the ForgeLoop-pinned tgrep version. | Provision the manifest-pinned tgrep release and retry; do not use an unpinned binary. |
|
|
1346
|
+
| `E_REPOSITORY_INDEX_INDEXING` | The repository index is still being built or reconciled and is not ready for the requested operation. | Wait for index-status to report READY, or use index-rebuild if the operation remains stuck. |
|
|
1347
|
+
| `E_REPOSITORY_INDEX_LOCK_UNSAFE` | A Repository Index operation lock is malformed, conflicting, or cannot be safely acquired. | Wait for a concurrent operation to finish and retry; use status or rebuild for a persistent lock failure. |
|
|
1348
|
+
| `E_REPOSITORY_INDEX_NOT_INITIALIZED` | The repository index has no complete derived index available for the requested operation. | Run forgeloop index-setup or forgeloop index-rebuild for the selected repository. |
|
|
1349
|
+
| `E_REPOSITORY_INDEX_OUTPUT_INVALID` | Native tgrep output did not match the bounded provider-neutral JSON contract. | Treat the result as unusable, inspect the engine, and rebuild or provision the pinned release. |
|
|
1350
|
+
| `E_REPOSITORY_INDEX_OUTPUT_LIMIT` | Managed tgrep output exceeded ForgeLoop's bounded process-output limit. | Narrow the search or resource policy and retry; oversized output is never promoted to a result. |
|
|
1351
|
+
| `E_REPOSITORY_INDEX_PLATFORM_UNSUPPORTED` | The current operating-system and architecture pair has no pinned tgrep release asset. | Use a supported platform or add a separately reviewed manifest asset; do not substitute a PATH executable. |
|
|
1352
|
+
| `E_REPOSITORY_INDEX_REBUILD_FAILED` | The derived repository index could not be rebuilt successfully. | Inspect the structured failure, resource policy, and repository paths, then retry index-rebuild. |
|
|
1353
|
+
| `E_REPOSITORY_INDEX_REQUEST_INVALID` | Repository Index command input is outside the bounded provider-neutral request contract. | Correct the pattern, path filters, context, or lifecycle options and retry the canonical command. |
|
|
1354
|
+
| `E_REPOSITORY_INDEX_SEARCH_FAILED` | The native tgrep search failed with an execution or provider error; no-match is not an error. | Inspect index-status and retry the query or rebuild the derived index; ForgeLoop does not fall back silently. |
|
|
1355
|
+
| `E_REPOSITORY_INDEX_SERVER_START_FAILED` | The ForgeLoop-owned tgrep watcher could not be started or did not become healthy. | Run index-status, inspect the structured reason, and retry index-start or index-rebuild. |
|
|
1356
|
+
| `E_REPOSITORY_INDEX_SERVER_STOP_FAILED` | The ForgeLoop-owned tgrep watcher could not be stopped or its ownership could not be proven. | Use index-status and retry the canonical stop operation; never terminate processes by name or PID alone. |
|
|
1357
|
+
| `E_REPOSITORY_INDEX_SERVER_UNHEALTHY` | Repository Index server, metadata, process identity, or index readiness validation failed. | Run index-status, then use index-rebuild after resolving the reported boundary. |
|
|
1211
1358
|
| `E_RESPONSIBILITY_FROZEN_INPUT_DRIFT` | A ForgeLoop boundary, artifact, provider, or attestation validation condition was not satisfied. | Inspect the structured command result, correct the named boundary or artifact, then retry the canonical command. |
|
|
1212
1359
|
| `E_RESPONSIBILITY_INVALID` | A ForgeLoop boundary, artifact, provider, or attestation validation condition was not satisfied. | Inspect the structured command result, correct the named boundary or artifact, then retry the canonical command. |
|
|
1213
1360
|
| `E_RESPONSIBILITY_REQUIRED_CHECK_MISSING` | A ForgeLoop boundary, artifact, provider, or attestation validation condition was not satisfied. | Inspect the structured command result, correct the named boundary or artifact, then retry the canonical command. |
|
|
@@ -67,6 +67,36 @@ may be absent only when the canonical protocol marks it not applicable;
|
|
|
67
67
|
presentation depth cannot change evidence, verification truth, authority,
|
|
68
68
|
provenance, safety-floor, or validator-backed completion requirements.
|
|
69
69
|
|
|
70
|
+
### Repository Search boundary
|
|
71
|
+
|
|
72
|
+
The Integration API exposes direct, transport-neutral repository operations:
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
import {
|
|
76
|
+
repositorySearch,
|
|
77
|
+
repositoryIndexStatus,
|
|
78
|
+
} from "@cassiomc1/forgeloop/integration";
|
|
79
|
+
|
|
80
|
+
const result = await repositorySearch({
|
|
81
|
+
projectPath: ".",
|
|
82
|
+
pattern: "needle",
|
|
83
|
+
fixedStrings: true,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const status = await repositoryIndexStatus({ projectPath: "." });
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
These operations call the canonical Repository Search service and require the
|
|
90
|
+
managed Repository Index for supported Git repositories. The shell-facing CLI
|
|
91
|
+
`search` command may use the user-scoped persistent local IPC host described in
|
|
92
|
+
[`PERSISTENT_SEARCH_TRANSPORT.md`](./PERSISTENT_SEARCH_TRANSPORT.md), but that
|
|
93
|
+
host is only a CLI startup/reuse optimization. Integration API and MCP callers
|
|
94
|
+
do not use it, and no transport carries lifecycle, claims, receipts, evidence,
|
|
95
|
+
or completion authority.
|
|
96
|
+
|
|
97
|
+
See [`REPOSITORY_INDEX.md`](./REPOSITORY_INDEX.md) for the normalized search
|
|
98
|
+
contract, tgrep management, status resource, and maintenance commands.
|
|
99
|
+
|
|
70
100
|
### Usage and efficiency boundary
|
|
71
101
|
|
|
72
102
|
Create a context with an optional trusted provider:
|
package/docs/diagrams/README.md
CHANGED
|
@@ -39,6 +39,15 @@ dark presentation so it remains legible in repository previews; the adjacent tex
|
|
|
39
39
|
[`README.md`](../../README.md#architecture-flow) carries the same lifecycle
|
|
40
40
|
semantics for text-only readers.
|
|
41
41
|
|
|
42
|
+
Advisory context, including the optional Ripwire adapter, is intentionally not
|
|
43
|
+
drawn as a lifecycle node or transition. It is a host-injected, explicit,
|
|
44
|
+
non-evidence input that can guide inspection while remaining outside state,
|
|
45
|
+
authority, verification, completion, and next-action decisions. Its boundary
|
|
46
|
+
is documented in [`ADVISORY_CONTEXT.md`](../ADVISORY_CONTEXT.md) and
|
|
47
|
+
[`RIPWIRE_ADAPTER.md`](../RIPWIRE_ADAPTER.md); keeping it out of these P0
|
|
48
|
+
visuals prevents an optional side channel from being mistaken for protocol
|
|
49
|
+
control flow.
|
|
50
|
+
|
|
42
51
|
Archify is vendored at `vendor/archify/v2.15.0/archify` under its MIT license.
|
|
43
52
|
The exact source commit and cryptographic vendor-tree hash are recorded in
|
|
44
53
|
`vendor/archify/v2.15.0/PIN.json`; generated-file hashes are recorded in the
|
|
@@ -78,3 +87,13 @@ These explanations are part of the JSON sources and generated explorers;
|
|
|
78
87
|
matching textual fallbacks live in the README, revision-provider guide, and
|
|
79
88
|
attestation guide. Re-render before renewing a visual review, then inspect
|
|
80
89
|
the new output at desktop and narrow widths before binding the review hashes.
|
|
90
|
+
|
|
91
|
+
The Repository Index and Persistent Search Transport are runtime discovery
|
|
92
|
+
boundaries rather than lifecycle transitions. Their current CLI/API/MCP
|
|
93
|
+
separation and local IPC sequence are documented in
|
|
94
|
+
[`REPOSITORY_INDEX.md`](../REPOSITORY_INDEX.md) and
|
|
95
|
+
[`PERSISTENT_SEARCH_TRANSPORT.md`](../PERSISTENT_SEARCH_TRANSPORT.md). They are
|
|
96
|
+
intentionally not added as a fourth Archify workflow merely to increase the
|
|
97
|
+
diagram count; the README hero provides the conceptual architecture overview,
|
|
98
|
+
while these three governed visuals remain focused on lifecycle, verification,
|
|
99
|
+
and attestation.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cassiomc1/forgeloop",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0",
|
|
4
4
|
"description": "Portable, verifiable engineering protocol for AI coding environments and developer workflows",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -24,6 +24,10 @@
|
|
|
24
24
|
"schemas",
|
|
25
25
|
"benchmarks/execution-profiles/*.json",
|
|
26
26
|
"benchmarks/execution-profiles/README.md",
|
|
27
|
+
"benchmarks/repository-index/queries.json",
|
|
28
|
+
"benchmarks/repository-index/README.md",
|
|
29
|
+
"benchmarks/repository-index/run-hot-path.mjs",
|
|
30
|
+
"benchmarks/repository-index/run-persistent-transport.mjs",
|
|
27
31
|
".forgeloop/forgeloop.gitignore",
|
|
28
32
|
"AGENTS.md",
|
|
29
33
|
"CLAUDE.md",
|
|
@@ -52,6 +56,7 @@
|
|
|
52
56
|
"docs/GETTING_STARTED.md",
|
|
53
57
|
"docs/CROSS_HARNESS_CONTINUITY.md",
|
|
54
58
|
"docs/ADVISORY_CONTEXT.md",
|
|
59
|
+
"docs/RIPWIRE_ADAPTER.md",
|
|
55
60
|
"docs/CLI_REFERENCE.md",
|
|
56
61
|
"docs/ARTIFACT_REFERENCE.md",
|
|
57
62
|
"docs/TROUBLESHOOTING.md",
|
|
@@ -71,6 +76,8 @@
|
|
|
71
76
|
"docs/PLATFORM_ADAPTERS.md",
|
|
72
77
|
"docs/AGENT_PROTOCOL_SUMMARY.md",
|
|
73
78
|
"docs/EXECUTION_PROFILE_BENCHMARKS.md",
|
|
79
|
+
"docs/REPOSITORY_INDEX.md",
|
|
80
|
+
"docs/PERSISTENT_SEARCH_TRANSPORT.md",
|
|
74
81
|
"docs/KNOWLEDGE_SOURCES.md",
|
|
75
82
|
"scripts/generate-shell-completions.mjs",
|
|
76
83
|
"scripts/generate-agent-protocol-summary.mjs",
|
|
@@ -79,6 +86,8 @@
|
|
|
79
86
|
"scripts/validate-execution-profile-benchmarks.mjs",
|
|
80
87
|
"scripts/report-execution-profile-outliers.mjs",
|
|
81
88
|
"scripts/report-tail-interpretation.mjs",
|
|
89
|
+
"scripts/verify-tgrep-manifest.mjs",
|
|
90
|
+
"scripts/update-tgrep-manifest.mjs",
|
|
82
91
|
"scripts/check-efficiency-regression.mjs",
|
|
83
92
|
"scripts/lib/execution-profile-benchmark-io.mjs",
|
|
84
93
|
"scripts/benchmark-cli-startup.mjs",
|
|
@@ -130,7 +139,8 @@
|
|
|
130
139
|
"benchmark:profiles:tail-analysis": "node scripts/report-tail-interpretation.mjs",
|
|
131
140
|
"benchmark:profiles:regression": "node scripts/check-efficiency-regression.mjs",
|
|
132
141
|
"transactions:compact": "node scripts/compact-transactions.mjs",
|
|
133
|
-
"complexity:check": "node scripts/check-complexity.mjs"
|
|
142
|
+
"complexity:check": "node scripts/check-complexity.mjs",
|
|
143
|
+
"repository-index:manifest": "node scripts/verify-tgrep-manifest.mjs"
|
|
134
144
|
},
|
|
135
145
|
"devDependencies": {
|
|
136
146
|
"c8": "^12.0.0",
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { execFile as nodeExecFile } from "node:child_process";
|
|
2
|
+
import { mkdtemp, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
import { validateTgrepManifest } from "../src/repository-index/manifest.js";
|
|
8
|
+
import { sha256File, validateArchiveEntry } from "../src/repository-index/binary-manager.js";
|
|
9
|
+
|
|
10
|
+
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
11
|
+
const manifestPath = path.join(packageRoot, "src", "repository-index", "tgrep-manifest.json");
|
|
12
|
+
const args = process.argv.slice(2);
|
|
13
|
+
const assetDirIndex = args.indexOf("--asset-dir");
|
|
14
|
+
const assetDir = assetDirIndex >= 0 ? args[assetDirIndex + 1] : null;
|
|
15
|
+
const shouldWrite = args.includes("--write");
|
|
16
|
+
|
|
17
|
+
if (!assetDir || !path.isAbsolute(assetDir) || !shouldWrite) {
|
|
18
|
+
console.error("Usage: node scripts/update-tgrep-manifest.mjs --asset-dir /absolute/release-assets --write");
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
|
|
23
|
+
validateTgrepManifest(manifest);
|
|
24
|
+
|
|
25
|
+
function execFile(file, args, options = {}) {
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
nodeExecFile(file, args, { shell: false, ...options }, (error, stdout, stderr) => {
|
|
28
|
+
if (error) {
|
|
29
|
+
error.stdout = stdout;
|
|
30
|
+
error.stderr = stderr;
|
|
31
|
+
reject(error);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
resolve({ stdout, stderr });
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function listArchiveEntries(archivePath, archive) {
|
|
40
|
+
const command = archive === "zip" && process.platform !== "win32" ? "unzip" : "tar";
|
|
41
|
+
const args = command === "unzip" ? ["-Z1", archivePath] : ["-tf", archivePath];
|
|
42
|
+
const result = await execFile(command, args, { maxBuffer: 2 * 1024 * 1024 });
|
|
43
|
+
return result.stdout.split(/\r?\n/).filter(Boolean).map(validateArchiveEntry);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function extractBinary(assetPath, asset, destination) {
|
|
47
|
+
const entries = await listArchiveEntries(assetPath, asset.archive);
|
|
48
|
+
if (!entries.some((entry) => path.posix.basename(entry) === asset.binaryName)) {
|
|
49
|
+
throw new Error(`archive does not contain ${asset.binaryName}`);
|
|
50
|
+
}
|
|
51
|
+
const command = asset.archive === "zip" && process.platform !== "win32" ? "unzip" : "tar";
|
|
52
|
+
const args = command === "unzip"
|
|
53
|
+
? ["-q", assetPath, "-d", destination]
|
|
54
|
+
: ["-xf", assetPath, "-C", destination];
|
|
55
|
+
await execFile(command, args, { maxBuffer: 2 * 1024 * 1024 });
|
|
56
|
+
|
|
57
|
+
const found = [];
|
|
58
|
+
async function visit(directory) {
|
|
59
|
+
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
60
|
+
const entryPath = path.join(directory, entry.name);
|
|
61
|
+
if (entry.isSymbolicLink()) throw new Error(`archive extracted a symbolic link: ${entry.name}`);
|
|
62
|
+
if (entry.isDirectory()) await visit(entryPath);
|
|
63
|
+
else if (entry.isFile() && entry.name === asset.binaryName) found.push(entryPath);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
await visit(destination);
|
|
67
|
+
if (found.length !== 1) throw new Error(`expected exactly one ${asset.binaryName}, found ${found.length}`);
|
|
68
|
+
return found[0];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
for (const asset of Object.values(manifest.assets)) {
|
|
72
|
+
const assetPath = path.join(assetDir, asset.assetName);
|
|
73
|
+
const info = await stat(assetPath);
|
|
74
|
+
if (!info.isFile()) throw new Error(`asset is not a regular file: ${assetPath}`);
|
|
75
|
+
asset.sha256 = await sha256File(assetPath);
|
|
76
|
+
const extractionDirectory = await mkdtemp(path.join(os.tmpdir(), "forgeloop-tgrep-manifest-"));
|
|
77
|
+
try {
|
|
78
|
+
const extractedBinary = await extractBinary(assetPath, asset, extractionDirectory);
|
|
79
|
+
asset.binarySha256 = await sha256File(extractedBinary);
|
|
80
|
+
} finally {
|
|
81
|
+
await rm(extractionDirectory, { recursive: true, force: true });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
validateTgrepManifest(manifest);
|
|
85
|
+
await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
86
|
+
console.log(`updated ${manifestPath}`);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
import { validateTgrepManifest } from "../src/repository-index/manifest.js";
|
|
6
|
+
|
|
7
|
+
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
|
+
const manifestPath = path.join(packageRoot, "src", "repository-index", "tgrep-manifest.json");
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
const value = JSON.parse(await readFile(manifestPath, "utf8"));
|
|
12
|
+
validateTgrepManifest(value);
|
|
13
|
+
console.log(`valid tgrep manifest: ${value.engine} ${value.version} (${Object.keys(value.assets).length} assets)`);
|
|
14
|
+
} catch (error) {
|
|
15
|
+
console.error(`invalid tgrep manifest: ${error.message}`);
|
|
16
|
+
process.exitCode = 1;
|
|
17
|
+
}
|