@nomos-arc/arc 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.
Files changed (160) hide show
  1. package/.claude/settings.local.json +10 -0
  2. package/.nomos-config.json +5 -0
  3. package/CLAUDE.md +108 -0
  4. package/LICENSE +190 -0
  5. package/README.md +569 -0
  6. package/dist/cli.js +21120 -0
  7. package/docs/auth/googel_plan.yaml +1093 -0
  8. package/docs/auth/google_task.md +235 -0
  9. package/docs/auth/hardened_blueprint.yaml +1658 -0
  10. package/docs/auth/red_team_report.yaml +336 -0
  11. package/docs/auth/session_state.yaml +162 -0
  12. package/docs/certificate/cer_enhance_plan.md +605 -0
  13. package/docs/certificate/certificate_report.md +338 -0
  14. package/docs/dev_overview.md +419 -0
  15. package/docs/feature_assessment.md +156 -0
  16. package/docs/how_it_works.md +78 -0
  17. package/docs/infrastructure/map.md +867 -0
  18. package/docs/init/master_plan.md +3581 -0
  19. package/docs/init/red_team_report.md +215 -0
  20. package/docs/init/report_phase_1a.md +304 -0
  21. package/docs/integrity-gate/enhance_drift.md +703 -0
  22. package/docs/integrity-gate/overview.md +108 -0
  23. package/docs/management/manger-task.md +99 -0
  24. package/docs/management/scafffold.md +76 -0
  25. package/docs/map/ATOMIC_BLUEPRINT.md +1349 -0
  26. package/docs/map/RED_TEAM_REPORT.md +159 -0
  27. package/docs/map/map_task.md +147 -0
  28. package/docs/map/semantic_graph_task.md +792 -0
  29. package/docs/map/semantic_master_plan.md +705 -0
  30. package/docs/phase7/TEAM_RED.md +249 -0
  31. package/docs/phase7/plan.md +1682 -0
  32. package/docs/phase7/task.md +275 -0
  33. package/docs/prompts/USAGE.md +312 -0
  34. package/docs/prompts/architect.md +165 -0
  35. package/docs/prompts/executer.md +190 -0
  36. package/docs/prompts/hardener.md +190 -0
  37. package/docs/prompts/red_team.md +146 -0
  38. package/docs/verification/goveranance-overview.md +396 -0
  39. package/docs/verification/governance-overview.md +245 -0
  40. package/docs/verification/verification-arc-ar.md +560 -0
  41. package/docs/verification/verification-architecture.md +560 -0
  42. package/docs/very_next.md +52 -0
  43. package/docs/whitepaper.md +89 -0
  44. package/overview.md +1469 -0
  45. package/package.json +63 -0
  46. package/src/adapters/__tests__/git.test.ts +296 -0
  47. package/src/adapters/__tests__/stdio.test.ts +70 -0
  48. package/src/adapters/git.ts +226 -0
  49. package/src/adapters/pty.ts +159 -0
  50. package/src/adapters/stdio.ts +113 -0
  51. package/src/cli.ts +83 -0
  52. package/src/commands/apply.ts +47 -0
  53. package/src/commands/auth.ts +301 -0
  54. package/src/commands/certificate.ts +89 -0
  55. package/src/commands/discard.ts +24 -0
  56. package/src/commands/drift.ts +116 -0
  57. package/src/commands/index.ts +78 -0
  58. package/src/commands/init.ts +121 -0
  59. package/src/commands/list.ts +75 -0
  60. package/src/commands/map.ts +55 -0
  61. package/src/commands/plan.ts +30 -0
  62. package/src/commands/review.ts +58 -0
  63. package/src/commands/run.ts +63 -0
  64. package/src/commands/search.ts +147 -0
  65. package/src/commands/show.ts +63 -0
  66. package/src/commands/status.ts +59 -0
  67. package/src/core/__tests__/budget.test.ts +213 -0
  68. package/src/core/__tests__/certificate.test.ts +385 -0
  69. package/src/core/__tests__/config.test.ts +191 -0
  70. package/src/core/__tests__/preflight.test.ts +24 -0
  71. package/src/core/__tests__/prompt.test.ts +358 -0
  72. package/src/core/__tests__/review.test.ts +161 -0
  73. package/src/core/__tests__/state.test.ts +362 -0
  74. package/src/core/auth/__tests__/manager.test.ts +166 -0
  75. package/src/core/auth/__tests__/server.test.ts +220 -0
  76. package/src/core/auth/gcp-projects.ts +160 -0
  77. package/src/core/auth/manager.ts +114 -0
  78. package/src/core/auth/server.ts +141 -0
  79. package/src/core/budget.ts +119 -0
  80. package/src/core/certificate.ts +502 -0
  81. package/src/core/config.ts +212 -0
  82. package/src/core/errors.ts +54 -0
  83. package/src/core/factory.ts +49 -0
  84. package/src/core/graph/__tests__/builder.test.ts +272 -0
  85. package/src/core/graph/__tests__/contract-writer.test.ts +175 -0
  86. package/src/core/graph/__tests__/enricher.test.ts +299 -0
  87. package/src/core/graph/__tests__/parser.test.ts +200 -0
  88. package/src/core/graph/__tests__/pipeline.test.ts +202 -0
  89. package/src/core/graph/__tests__/renderer.test.ts +128 -0
  90. package/src/core/graph/__tests__/resolver.test.ts +185 -0
  91. package/src/core/graph/__tests__/scanner.test.ts +231 -0
  92. package/src/core/graph/__tests__/show.test.ts +134 -0
  93. package/src/core/graph/builder.ts +303 -0
  94. package/src/core/graph/constraints.ts +94 -0
  95. package/src/core/graph/contract-writer.ts +93 -0
  96. package/src/core/graph/drift/__tests__/classifier.test.ts +215 -0
  97. package/src/core/graph/drift/__tests__/comparator.test.ts +335 -0
  98. package/src/core/graph/drift/__tests__/drift.test.ts +453 -0
  99. package/src/core/graph/drift/__tests__/reporter.test.ts +203 -0
  100. package/src/core/graph/drift/classifier.ts +165 -0
  101. package/src/core/graph/drift/comparator.ts +205 -0
  102. package/src/core/graph/drift/reporter.ts +77 -0
  103. package/src/core/graph/enricher.ts +251 -0
  104. package/src/core/graph/grammar-paths.ts +30 -0
  105. package/src/core/graph/html-template.ts +493 -0
  106. package/src/core/graph/map-schema.ts +137 -0
  107. package/src/core/graph/parser.ts +336 -0
  108. package/src/core/graph/pipeline.ts +209 -0
  109. package/src/core/graph/renderer.ts +92 -0
  110. package/src/core/graph/resolver.ts +195 -0
  111. package/src/core/graph/scanner.ts +145 -0
  112. package/src/core/logger.ts +46 -0
  113. package/src/core/orchestrator.ts +792 -0
  114. package/src/core/plan-file-manager.ts +66 -0
  115. package/src/core/preflight.ts +64 -0
  116. package/src/core/prompt.ts +173 -0
  117. package/src/core/review.ts +95 -0
  118. package/src/core/state.ts +294 -0
  119. package/src/core/worktree-coordinator.ts +77 -0
  120. package/src/search/__tests__/chunk-extractor.test.ts +339 -0
  121. package/src/search/__tests__/embedder-auth.test.ts +124 -0
  122. package/src/search/__tests__/embedder.test.ts +267 -0
  123. package/src/search/__tests__/graph-enricher.test.ts +178 -0
  124. package/src/search/__tests__/indexer.test.ts +518 -0
  125. package/src/search/__tests__/integration.test.ts +649 -0
  126. package/src/search/__tests__/query-engine.test.ts +334 -0
  127. package/src/search/__tests__/similarity.test.ts +78 -0
  128. package/src/search/__tests__/vector-store.test.ts +281 -0
  129. package/src/search/chunk-extractor.ts +167 -0
  130. package/src/search/embedder.ts +209 -0
  131. package/src/search/graph-enricher.ts +95 -0
  132. package/src/search/indexer.ts +483 -0
  133. package/src/search/lexical-searcher.ts +190 -0
  134. package/src/search/query-engine.ts +225 -0
  135. package/src/search/vector-store.ts +311 -0
  136. package/src/types/index.ts +572 -0
  137. package/src/utils/__tests__/ansi.test.ts +54 -0
  138. package/src/utils/__tests__/frontmatter.test.ts +79 -0
  139. package/src/utils/__tests__/sanitize.test.ts +229 -0
  140. package/src/utils/ansi.ts +19 -0
  141. package/src/utils/context.ts +44 -0
  142. package/src/utils/frontmatter.ts +27 -0
  143. package/src/utils/sanitize.ts +78 -0
  144. package/test/e2e/lifecycle.test.ts +330 -0
  145. package/test/fixtures/mock-planner-hang.ts +5 -0
  146. package/test/fixtures/mock-planner.ts +26 -0
  147. package/test/fixtures/mock-reviewer-bad.ts +8 -0
  148. package/test/fixtures/mock-reviewer-retry.ts +34 -0
  149. package/test/fixtures/mock-reviewer.ts +18 -0
  150. package/test/fixtures/sample-project/src/circular-a.ts +6 -0
  151. package/test/fixtures/sample-project/src/circular-b.ts +6 -0
  152. package/test/fixtures/sample-project/src/config.ts +15 -0
  153. package/test/fixtures/sample-project/src/main.ts +19 -0
  154. package/test/fixtures/sample-project/src/services/product-service.ts +20 -0
  155. package/test/fixtures/sample-project/src/services/user-service.ts +18 -0
  156. package/test/fixtures/sample-project/src/types.ts +14 -0
  157. package/test/fixtures/sample-project/src/utils/index.ts +14 -0
  158. package/test/fixtures/sample-project/src/utils/validate.ts +12 -0
  159. package/tsconfig.json +20 -0
  160. package/vitest.config.ts +12 -0
@@ -0,0 +1,235 @@
1
+ # Blueprint: nomos-arc.ai Frictionless Authentication — OAuth 2.0 Login
2
+
3
+ **Role:** Senior Full-Stack Engineer / CLI Specialist.
4
+ **Objective:** Implement a "One-Click" Google Login for the `arc` CLI to replace manual `GEMINI_API_KEY` environment variable management.
5
+ **Architecture:** OAuth 2.0 Authorization Code Flow with Local Loopback Server.
6
+
7
+ ---
8
+
9
+ ## 0. Motivation & Scope
10
+
11
+ Currently, all Gemini API calls (`Embedder`, `enricher`) require `process.env['GEMINI_API_KEY']` to be set manually. This creates friction for onboarding and breaks the "local-first, offline-capable" developer experience.
12
+
13
+ This task introduces an **OAuth 2.0 credential chain** that sits *behind* the existing API key check — if `GEMINI_API_KEY` is absent, the system transparently falls back to stored OAuth tokens. The API key path remains the primary, zero-config option for CI/CD and headless environments.
14
+
15
+ **In scope:** `arc auth login`, `arc auth logout`, `arc auth status`, token persistence, credential chain fallback in `Embedder` and `enricher`.
16
+ **Out of scope:** Google Cloud project provisioning, Vertex AI authentication, multi-account switching, UI/dashboard.
17
+
18
+ ---
19
+
20
+ ## 1. Technical Specifications
21
+
22
+ | Feature | Specification |
23
+ | :--- | :--- |
24
+ | **Protocol** | OAuth 2.0 Authorization Code Flow (Desktop/CLI) |
25
+ | **Scopes** | `https://www.googleapis.com/auth/generative-language` |
26
+ | **Token Storage** | `~/.nomos/credentials.json` (file mode `0600`) |
27
+ | **Client Config** | `~/.nomos/client_config.json` (user-provided `client_id` / `client_secret`) |
28
+ | **Dependencies** | `google-auth-library`, `open` (browser launcher), Node.js built-in `http` |
29
+ | **Auth Provider** | Google Generative Language API |
30
+ | **Redirect URI** | `http://localhost:<dynamic-port>` (port selected at runtime) |
31
+
32
+ ---
33
+
34
+ ## 2. Design Constraints (Alignment with nomos-arc.ai Architecture)
35
+
36
+ These constraints ensure the auth module integrates cleanly with existing patterns:
37
+
38
+ 1. **JSON is the source of truth.** Token state lives in `~/.nomos/credentials.json` (JSON). No Markdown side-files. Consistent with the project's "JSON source of truth; Markdown for humans" contract.
39
+ 2. **Error codes via `NomosError`.** All auth failures must use typed error codes registered in `src/core/errors.ts` (e.g., `auth_not_logged_in`, `auth_token_expired`, `auth_login_failed`). Never throw raw `Error`.
40
+ 3. **Commander.js registration pattern.** New commands follow the `registerXCommand(program)` pattern used by every existing command in `src/commands/`. Registered in `src/cli.ts`.
41
+ 4. **Config-driven.** OAuth client configuration should be loadable from `.nomos-config.json` under an `auth` key, with `~/.nomos/client_config.json` as the user-level override.
42
+ 5. **Security contract.** `client_secret` must never be logged. The sanitization pipeline (`src/utils/sanitize.ts`) must cover auth tokens if `security.sanitize_on` includes `"output"`.
43
+ 6. **No global state.** `AuthManager` is instantiated per-command, same as `Embedder`. No singletons, no module-level side effects.
44
+ 7. **Logging via Winston.** Use the project's existing `Logger` instance — no `console.log`.
45
+
46
+ ---
47
+
48
+ ## 3. Atomic Implementation Tasks
49
+
50
+ ### Task 0: Scaffolding & Types
51
+
52
+ | Sub-task | Action | Validation |
53
+ | :--- | :--- | :--- |
54
+ | **0.1** | Install dependency: `npm install google-auth-library open` | `package.json` updated |
55
+ | **0.2** | Add `AuthCredentials` interface to `src/types/index.ts`: `{ access_token, refresh_token, expiry_date, token_type, scope }` | TypeScript compiles |
56
+ | **0.3** | Add `auth` section to `NomosConfig` in `src/types/index.ts`: `{ client_id?: string, client_secret?: string, credentials_path: string }` with default `~/.nomos/credentials.json` | Config schema validates |
57
+ | **0.4** | Register new error codes in `src/core/errors.ts`: `auth_not_logged_in`, `auth_token_expired`, `auth_login_failed`, `auth_client_config_missing` | Type-checks pass |
58
+ | **0.5** | Create directory: `src/core/auth/` | Directory exists |
59
+
60
+ ### Task 1: Auth Manager (`src/core/auth/manager.ts`)
61
+
62
+ | Sub-task | Action | Validation |
63
+ | :--- | :--- | :--- |
64
+ | **1.1** | Implement `AuthManager` class accepting `NomosConfig['auth']` and `Logger` | Constructor doesn't throw |
65
+ | **1.2** | Method `saveCredentials(tokens: AuthCredentials): Promise<void>` — write JSON to `credentials_path` with `fs.chmod(path, 0o600)` | File written with mode 600 |
66
+ | **1.3** | Method `loadCredentials(): AuthCredentials \| null` — read and parse JSON, return `null` if file missing | Returns null on clean system |
67
+ | **1.4** | Method `getAuthenticatedClient(): Promise<OAuth2Client>` — load tokens, check `expiry_date < Date.now()`, auto-refresh if expired via `oauth2Client.refreshAccessToken()`, persist new tokens | Refreshed token persisted |
68
+ | **1.5** | Method `isLoggedIn(): boolean` — check file exists and contains `refresh_token` | Returns boolean |
69
+ | **1.6** | Method `clearCredentials(): Promise<void>` — delete credentials file | File removed |
70
+
71
+ ### Task 2: Loopback Server (`src/core/auth/server.ts`)
72
+
73
+ | Sub-task | Action | Validation |
74
+ | :--- | :--- | :--- |
75
+ | **2.1** | Implement `startLoopbackServer(oauth2Client, logger): Promise<AuthCredentials>` using Node.js built-in `http` module | Function signature correct |
76
+ | **2.2** | Dynamically select an available port using `net.createServer().listen(0)` pattern (no external dependency needed) | Port assigned without conflict |
77
+ | **2.3** | Generate auth URL: `oauth2Client.generateAuthUrl({ access_type: 'offline', scope: SCOPES, redirect_uri })` | URL contains correct params |
78
+ | **2.4** | Open browser via `open(authUrl)` — log the URL as fallback if browser launch fails | Browser opens or URL printed |
79
+ | **2.5** | Handle callback: extract `code` from `req.url`, exchange via `oauth2Client.getToken(code)`, respond with success HTML, destroy server | Tokens returned |
80
+ | **2.6** | Implement 120-second timeout — if no callback received, destroy server and throw `auth_login_failed` | Server cleaned up on timeout |
81
+ | **2.7** | Use `server.close()` + `socket.destroy()` pattern (track open sockets) to ensure no hanging connections | Process exits cleanly |
82
+
83
+ ### Task 3: CLI Commands (`src/commands/auth.ts`)
84
+
85
+ Commands are registered as a **subcommand group** under `arc auth`:
86
+
87
+ ```
88
+ arc auth login # OAuth login flow
89
+ arc auth logout # Remove stored credentials
90
+ arc auth status # Show current auth state
91
+ ```
92
+
93
+ | Sub-task | Action | Validation |
94
+ | :--- | :--- | :--- |
95
+ | **3.1** | Create `auth` subcommand group using Commander.js `.command('auth').description(...)` with nested `.command('login')`, `.command('logout')`, `.command('status')` | `arc auth --help` lists all subcommands |
96
+ | **3.2** | `arc auth login` — create `OAuth2Client` from config, call `startLoopbackServer()`, save tokens via `AuthManager.saveCredentials()`, print success message | `credentials.json` created after login |
97
+ | **3.3** | `arc auth login` — if `client_id` / `client_secret` not in config, prompt user interactively (or accept via `--client-id` / `--client-secret` flags) | Works with flags and interactive prompt |
98
+ | **3.4** | `arc auth logout` — call `AuthManager.clearCredentials()`, confirm deletion to user | File removed, confirmation printed |
99
+ | **3.5** | `arc auth status` — display login state: logged in (email, token expiry) or not logged in, and whether `GEMINI_API_KEY` is set | Accurate status printed |
100
+ | **3.6** | Register in `src/cli.ts` via `registerAuthCommand(program)` pattern | `arc auth` appears in `arc --help` |
101
+
102
+ ### Task 4: Credential Chain Integration
103
+
104
+ | Sub-task | Action | Validation |
105
+ | :--- | :--- | :--- |
106
+ | **4.1** | Modify `src/search/embedder.ts` constructor: if `GEMINI_API_KEY` is absent, attempt `AuthManager.getAuthenticatedClient()` | Embedder initializes without env var |
107
+ | **4.2** | Modify `src/core/graph/enricher.ts` constructor: same credential chain fallback | Enricher initializes without env var |
108
+ | **4.3** | Throw `search_api_key_missing` **only** if both API key and OAuth credentials are unavailable. Update error message: `"No credentials found. Set GEMINI_API_KEY or run: arc auth login"` | Error message updated |
109
+ | **4.4** | Ensure `GoogleGenerativeAI` client can accept `OAuth2Client` auth — if SDK requires API key string only, use `oauth2Client.getAccessToken()` and pass as key | API calls succeed with OAuth token |
110
+
111
+ ### Task 5: Testing
112
+
113
+ | Sub-task | Action | Validation |
114
+ | :--- | :--- | :--- |
115
+ | **5.1** | Unit tests for `AuthManager`: save/load/clear/isLoggedIn/refresh cycle | All pass |
116
+ | **5.2** | Unit test for loopback server: mock HTTP callback, verify token exchange | Pass |
117
+ | **5.3** | Integration test: credential chain in `Embedder` — mock OAuth path when no API key | Embedder constructs without `GEMINI_API_KEY` |
118
+ | **5.4** | Test file permissions: verify `credentials.json` is written with mode `0600` | Permission check passes |
119
+
120
+ ---
121
+
122
+ ## 4. Authentication Flow (Sequence)
123
+
124
+ ```
125
+ User CLI (arc auth login) Browser Google OAuth
126
+ | | | |
127
+ |--- arc auth login ------>| | |
128
+ | |-- start HTTP server | |
129
+ | | (localhost:<port>) | |
130
+ | |-- open(authUrl) -------->| |
131
+ | | |--- user approves --->|
132
+ | | |<-- redirect ---------|
133
+ | | | ?code=XYZ |
134
+ | |<-- GET /?code=XYZ -------| |
135
+ | |-- exchange code -------->|--------------------->|
136
+ | |<-- tokens ---------------|<---------------------|
137
+ | |-- save credentials.json | |
138
+ | |-- destroy server | |
139
+ |<-- "Logged in!" ---------| | |
140
+ | | | |
141
+ |--- arc search "query" -->| | |
142
+ | |-- no GEMINI_API_KEY | |
143
+ | |-- load credentials.json | |
144
+ | |-- use OAuth2Client ----->|--------------------->|
145
+ | |<-- embeddings -----------|<---------------------|
146
+ |<-- search results -------| | |
147
+ ```
148
+
149
+ ---
150
+
151
+ ## 5. Credential Chain Resolution Order
152
+
153
+ The following priority chain applies in `Embedder` and `enricher` constructors:
154
+
155
+ ```
156
+ 1. process.env['GEMINI_API_KEY'] → GoogleGenerativeAI(apiKey) [existing path]
157
+ 2. ~/.nomos/credentials.json → OAuth2Client(accessToken) [new path]
158
+ 3. Neither available → throw NomosError('search_api_key_missing', ...)
159
+ ```
160
+
161
+ This preserves full backward compatibility. Existing users with `GEMINI_API_KEY` set see zero behavior change.
162
+
163
+ ---
164
+
165
+ ## 6. Known Risks & Migration Notes
166
+
167
+ ### Risk 1: Sync → Async Constructor (High Impact)
168
+
169
+ `Embedder` and `SemanticEnricher` currently have **synchronous constructors** that read `GEMINI_API_KEY` from `process.env`. The OAuth fallback requires async operations (file I/O, token refresh). This means the constructor cannot remain synchronous.
170
+
171
+ **Mitigation:** Introduce a static async factory method `Embedder.create(config, logger, authManager?)` and update all call sites. The existing `new Embedder(config, logger)` pattern must be replaced project-wide. All existing tests that use `new Embedder(...)` must be updated.
172
+
173
+ **Affected call sites:** `src/search/indexer.ts`, `src/search/query-engine.ts`, `src/core/graph/pipeline.ts`, and their test files.
174
+
175
+ ### Risk 2: `@google/generative-ai` SDK + OAuth Token Compatibility (Medium Impact)
176
+
177
+ The current SDK (`@google/generative-ai: ^0.24.1`) accepts an **API key string** in its constructor — not an `OAuth2Client`. There is no documented way to pass OAuth bearer tokens directly.
178
+
179
+ **Mitigation options (investigate during Task 4):**
180
+ 1. Pass `access_token` as the API key string — some Google APIs accept this, but it's undocumented for this SDK. **Requires a spike test.**
181
+ 2. Switch to `@google-ai/generativelanguage` (lower-level SDK) which accepts `authClient` directly — but this changes the API surface for embedding and enrichment calls.
182
+ 3. Use `google-auth-library`'s `OAuth2Client` to manually set `Authorization: Bearer <token>` headers via a custom request interceptor.
183
+
184
+ **Recommendation:** Start with option 1 (spike test). If it fails, fall back to option 3 which is non-invasive. Option 2 is last resort as it requires rewriting `Embedder` and `SemanticEnricher` internals.
185
+
186
+ ### Risk 3: Dynamic Port + Google Cloud Console Redirect URI
187
+
188
+ Google OAuth requires the redirect URI to **exactly match** what's configured in the Cloud Console. A dynamic port (`listen(0)`) means the URI changes every time.
189
+
190
+ **Mitigation:** Use a **fixed default port** (e.g., `3000`) with automatic fallback to a random port. Document that users should register `http://localhost:3000` in their Cloud Console. Alternatively, support a configurable `auth.redirect_port` in `.nomos-config.json`. Google Cloud Console also supports `http://localhost` (without port) for desktop apps — verify this during implementation.
191
+
192
+ ---
193
+
194
+ ## 7. Acceptance Criteria
195
+
196
+ - [ ] `arc auth login` opens the default system browser and completes OAuth flow end-to-end.
197
+ - [ ] After Google approval, `~/.nomos/credentials.json` is written with `refresh_token` for persistent sessions.
198
+ - [ ] File permissions for `credentials.json` are `0600` (owner-only read/write).
199
+ - [ ] `arc search` and `arc map` (with `--ai-enrichment`) work without `GEMINI_API_KEY` when OAuth credentials exist.
200
+ - [ ] `arc search` and `arc map` still work with `GEMINI_API_KEY` (backward compatible, takes priority).
201
+ - [ ] `arc auth logout` removes `credentials.json` and confirms to user.
202
+ - [ ] `arc auth status` accurately reports login state and API key presence.
203
+ - [ ] If both API key and OAuth are missing, error message mentions both `GEMINI_API_KEY` and `arc auth login`.
204
+ - [ ] Loopback server self-destructs after receiving callback or after 120s timeout.
205
+ - [ ] No `client_secret` or token values appear in Winston logs.
206
+ - [ ] All new code follows existing patterns: `NomosError` codes, Commander.js registration, Winston logger injection.
207
+ - [ ] Existing tests pass without modification (unless they touch refactored constructor signatures).
208
+
209
+ ---
210
+
211
+ ## 8. Files Modified / Created
212
+
213
+ | File | Action | Description |
214
+ | :--- | :--- | :--- |
215
+ | `src/types/index.ts` | **Modify** | Add `AuthCredentials` interface, `auth` config section |
216
+ | `src/core/errors.ts` | **Modify** | Add `auth_*` error codes to `NomosErrorCode` union |
217
+ | `src/core/auth/manager.ts` | **Create** | Token persistence, refresh, credential chain |
218
+ | `src/core/auth/server.ts` | **Create** | OAuth loopback HTTP server |
219
+ | `src/commands/auth.ts` | **Create** | `arc auth` subcommand group (login / logout / status) |
220
+ | `src/cli.ts` | **Modify** | Register `registerAuthCommand` |
221
+ | `src/core/config.ts` | **Modify** | Add `AuthSchema` with defaults to Zod config |
222
+ | `src/search/embedder.ts` | **Modify** | Credential chain fallback in constructor |
223
+ | `src/core/graph/enricher.ts` | **Modify** | Credential chain fallback in constructor |
224
+ | `package.json` | **Modify** | Add `google-auth-library`, `open` dependencies |
225
+
226
+ ---
227
+
228
+ ## 9. Implementation Notes
229
+
230
+ - **No `portfinder` dependency.** Use Node.js built-in `net.createServer().listen(0)` to get a random available port. Fewer dependencies = smaller attack surface.
231
+ - **`client_id` / `client_secret` handling:** These are user-provided (from their own Google Cloud Console project). They are NOT embedded in the CLI binary. Users provide them on first `arc auth login` or via `.nomos-config.json`.
232
+ - **Socket tracking for clean shutdown:** Track connected sockets in a `Set<net.Socket>`, destroy all on server close. This replaces the need for `server-destroy`.
233
+ - **`open` package:** Used only for browser launch. If it fails (headless server), log the URL for manual copy-paste.
234
+ - **Token refresh is transparent:** `getAuthenticatedClient()` checks `expiry_date` and refreshes before returning. Callers never deal with expiry logic.
235
+ - **Redirect URI must match exactly** what is configured in the Google Cloud Console (e.g., `http://localhost:PORT`). Since the port is dynamic, the Google Cloud Console OAuth config must use `http://localhost` with the port registered as a wildcard or the user must configure a fixed port.