@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.
Files changed (92) hide show
  1. package/.cursor/rules/project-loop.mdc +2 -2
  2. package/.forgeloop/forgeloop.gitignore +1 -0
  3. package/.github/copilot-instructions.md +2 -2
  4. package/AGENTS.md +1 -0
  5. package/AGENT_COMPATIBILITY.md +7 -0
  6. package/CLAUDE.md +1 -0
  7. package/DOCS_INDEX.md +13 -1
  8. package/LOOP_SYSTEM_DESIGN.md +12 -0
  9. package/ORCHESTRATOR_INTEGRATION.md +9 -0
  10. package/PROTOCOL_INTEGRATION.md +17 -0
  11. package/README.md +21 -18
  12. package/THIRD_PARTY_NOTICES.md +11 -0
  13. package/THREAT_MODEL.md +21 -0
  14. package/benchmarks/repository-index/README.md +73 -0
  15. package/benchmarks/repository-index/queries.json +12 -0
  16. package/benchmarks/repository-index/run-hot-path.mjs +142 -0
  17. package/benchmarks/repository-index/run-persistent-transport.mjs +169 -0
  18. package/completions/_forgeloop +7 -1
  19. package/completions/forgeloop.bash +13 -1
  20. package/completions/forgeloop.fish +40 -1
  21. package/docs/ADVISORY_CONTEXT.md +24 -0
  22. package/docs/AGENT_PROTOCOL_SUMMARY.md +8 -1
  23. package/docs/CLI_REFERENCE.md +114 -2
  24. package/docs/DOCUMENTATION_GUIDE.md +10 -5
  25. package/docs/GETTING_STARTED.md +17 -0
  26. package/docs/MCP.md +16 -1
  27. package/docs/PACKAGE_CONTENTS.md +12 -1
  28. package/docs/PERSISTENT_SEARCH_TRANSPORT.md +289 -0
  29. package/docs/RECIPES.md +30 -0
  30. package/docs/RELEASE_CHECKLIST.md +36 -2
  31. package/docs/REPOSITORY_INDEX.md +553 -0
  32. package/docs/RIPWIRE_ADAPTER.md +189 -0
  33. package/docs/TROUBLESHOOTING.md +147 -0
  34. package/docs/UNIVERSAL_INTEGRATION.md +30 -0
  35. package/docs/diagrams/README.md +19 -0
  36. package/package.json +12 -2
  37. package/scripts/update-tgrep-manifest.mjs +86 -0
  38. package/scripts/verify-tgrep-manifest.mjs +17 -0
  39. package/src/adapters/ripwire/normalize.js +352 -0
  40. package/src/adapters/ripwire/process.js +248 -0
  41. package/src/adapters/ripwire/provider.js +245 -0
  42. package/src/cli.js +35 -0
  43. package/src/commands/doctor.js +76 -1
  44. package/src/commands/index-rebuild.js +1 -0
  45. package/src/commands/index-setup.js +1 -0
  46. package/src/commands/index-start.js +1 -0
  47. package/src/commands/index-status.js +1 -0
  48. package/src/commands/index-stop.js +1 -0
  49. package/src/commands/init.js +39 -1
  50. package/src/commands/repository-index.js +111 -0
  51. package/src/commands/search.js +1 -0
  52. package/src/commands/update.js +32 -4
  53. package/src/core/advisory-context/service.js +36 -14
  54. package/src/core/cli-command-definitions.js +97 -3
  55. package/src/core/command-executors.js +35 -2
  56. package/src/core/command-input.js +23 -0
  57. package/src/core/error-codes.js +195 -0
  58. package/src/core/filesystem.js +10 -1
  59. package/src/core/handoff-acceptance.js +10 -1
  60. package/src/core/integration-invocation-policy.js +27 -0
  61. package/src/core/integration-resources.js +16 -1
  62. package/src/core/protocol-info.js +23 -0
  63. package/src/core/work-state.js +15 -6
  64. package/src/integration.d.ts +101 -0
  65. package/src/integration.js +22 -0
  66. package/src/persistent-transport/client.js +293 -0
  67. package/src/persistent-transport/constants.js +24 -0
  68. package/src/persistent-transport/errors.js +38 -0
  69. package/src/persistent-transport/framing.js +61 -0
  70. package/src/persistent-transport/lifecycle.js +116 -0
  71. package/src/persistent-transport/ownership.js +184 -0
  72. package/src/persistent-transport/paths.js +31 -0
  73. package/src/persistent-transport/protocol.js +95 -0
  74. package/src/persistent-transport/server.js +256 -0
  75. package/src/persistent-transport/state.js +49 -0
  76. package/src/repository-index/args.js +59 -0
  77. package/src/repository-index/binary-manager.js +413 -0
  78. package/src/repository-index/constants.js +45 -0
  79. package/src/repository-index/errors.js +38 -0
  80. package/src/repository-index/lifecycle.js +17 -0
  81. package/src/repository-index/lock.js +113 -0
  82. package/src/repository-index/manifest.js +132 -0
  83. package/src/repository-index/metrics.js +30 -0
  84. package/src/repository-index/normalize-json.js +187 -0
  85. package/src/repository-index/paths.js +39 -0
  86. package/src/repository-index/platform.js +20 -0
  87. package/src/repository-index/process.js +140 -0
  88. package/src/repository-index/readiness.js +62 -0
  89. package/src/repository-index/search.js +262 -0
  90. package/src/repository-index/server.js +432 -0
  91. package/src/repository-index/status.js +397 -0
  92. 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.
@@ -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:
@@ -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.10.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
+ }