aios-core 4.2.12 → 4.2.13

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.
@@ -0,0 +1,331 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const yaml = require('js-yaml');
6
+ const { getClient, isCodeIntelAvailable } = require('../code-intel');
7
+ const { RegistryLoader, DEFAULT_REGISTRY_PATH } = require('../ids/registry-loader');
8
+
9
+ // Role inference from entity path (order matters: more specific patterns first)
10
+ const ROLE_MAP = [
11
+ ['tasks/', 'task'],
12
+ ['templates/', 'template'],
13
+ ['agents/', 'agent'],
14
+ ['workflows/', 'workflow'],
15
+ ['scripts/', 'script'],
16
+ ['/data/', 'config'],
17
+ ['/core/', 'module'],
18
+ ];
19
+
20
+ /**
21
+ * Infer entity role from its file path.
22
+ * @param {string} entityPath - Entity source path
23
+ * @returns {string} Inferred role
24
+ */
25
+ function inferRole(entityPath) {
26
+ if (!entityPath) return 'unknown';
27
+ const normalized = entityPath.replace(/\\/g, '/');
28
+ for (const [pattern, role] of ROLE_MAP) {
29
+ if (normalized.includes(pattern)) return role;
30
+ }
31
+ return 'unknown';
32
+ }
33
+
34
+ /**
35
+ * RegistrySyncer — Enriches entity registry with code intelligence data.
36
+ *
37
+ * Features:
38
+ * - Batch enrichment of usedBy, dependencies, keywords via code intelligence
39
+ * - Incremental sync (mtime-based) and full resync (--full flag)
40
+ * - Atomic write (temp file + rename) to prevent registry corruption
41
+ * - Graceful fallback when no provider is available
42
+ */
43
+ class RegistrySyncer {
44
+ /**
45
+ * @param {Object} [options]
46
+ * @param {string} [options.registryPath] - Path to entity-registry.yaml
47
+ * @param {string} [options.repoRoot] - Repository root for resolving entity paths
48
+ * @param {Object} [options.client] - Code intel client (for testing injection)
49
+ * @param {Function} [options.logger] - Logger function (defaults to console.log)
50
+ */
51
+ constructor(options = {}) {
52
+ this._registryPath = options.registryPath || DEFAULT_REGISTRY_PATH;
53
+ this._repoRoot = options.repoRoot || path.resolve(__dirname, '../../../');
54
+ this._client = options.client || null;
55
+ this._logger = options.logger || console.log;
56
+ this._stats = { processed: 0, skipped: 0, errors: 0, total: 0 };
57
+ }
58
+
59
+ /**
60
+ * Get code intel client (lazy, allows injection for testing).
61
+ * @returns {Object|null}
62
+ */
63
+ _getClient() {
64
+ if (this._client) return this._client;
65
+ return getClient();
66
+ }
67
+
68
+ /**
69
+ * Sync the entity registry with code intelligence data.
70
+ * @param {Object} [options]
71
+ * @param {boolean} [options.full=false] - Force full resync (ignore lastSynced)
72
+ * @returns {Promise<Object>} Sync stats
73
+ */
74
+ async sync(options = {}) {
75
+ const isFull = options.full === true;
76
+
77
+ // AC5: Fallback — check provider availability first
78
+ if (!this._isProviderAvailable()) {
79
+ this._logger('[registry-syncer] No code intelligence provider available, skipping enrichment');
80
+ return { processed: 0, skipped: 0, errors: 0, total: 0, aborted: true };
81
+ }
82
+
83
+ // Load registry
84
+ const loader = new RegistryLoader(this._registryPath);
85
+ const registry = loader.load();
86
+ const entities = registry.entities || {};
87
+
88
+ // Flatten all entities for iteration
89
+ const allEntities = [];
90
+ for (const [category, categoryEntities] of Object.entries(entities)) {
91
+ if (!categoryEntities || typeof categoryEntities !== 'object') continue;
92
+ for (const [entityId, entityData] of Object.entries(categoryEntities)) {
93
+ allEntities.push({ id: entityId, category, data: entityData });
94
+ }
95
+ }
96
+
97
+ this._stats = { processed: 0, skipped: 0, errors: 0, total: allEntities.length };
98
+ this._logger(`[registry-syncer] Starting ${isFull ? 'full' : 'incremental'} sync of ${allEntities.length} entities`);
99
+
100
+ // Iterate and enrich
101
+ for (const entity of allEntities) {
102
+ try {
103
+ const wasProcessed = await this.syncEntity(entity, entities, isFull);
104
+ if (wasProcessed) {
105
+ this._stats.processed++;
106
+ } else {
107
+ this._stats.skipped++;
108
+ }
109
+ } catch (error) {
110
+ this._stats.errors++;
111
+ this._logger(`[registry-syncer] Error enriching ${entity.id}: ${error.message}`);
112
+ }
113
+ }
114
+
115
+ // Update metadata
116
+ registry.metadata = registry.metadata || {};
117
+ registry.metadata.lastUpdated = new Date().toISOString();
118
+ registry.metadata.entityCount = allEntities.length;
119
+
120
+ // Atomic write
121
+ this._atomicWrite(this._registryPath, registry);
122
+
123
+ this._logger(`[registry-syncer] Sync complete: ${this._stats.processed} processed, ${this._stats.skipped} skipped, ${this._stats.errors} errors`);
124
+ return { ...this._stats };
125
+ }
126
+
127
+ /**
128
+ * Enrich a single entity with code intelligence data.
129
+ * @param {Object} entity - { id, category, data }
130
+ * @param {Object} entities - Full entities map (for cross-reference)
131
+ * @param {boolean} isFull - Force full resync
132
+ * @returns {Promise<boolean>} true if entity was processed, false if skipped
133
+ */
134
+ async syncEntity(entity, entities, isFull) {
135
+ const { id, data } = entity;
136
+ const sourcePath = data.path;
137
+
138
+ // Skip entities without source path
139
+ if (!sourcePath) {
140
+ return false;
141
+ }
142
+
143
+ // AC6: Incremental sync — check mtime vs lastSynced
144
+ if (!isFull) {
145
+ const shouldSkip = this._shouldSkipIncremental(data);
146
+ if (shouldSkip) {
147
+ return false;
148
+ }
149
+ }
150
+
151
+ const client = this._getClient();
152
+ const now = new Date().toISOString();
153
+
154
+ // AC2: Populate usedBy via findReferences
155
+ const usedByIds = await this._findUsedBy(client, id, entities);
156
+ if (usedByIds !== null) {
157
+ data.usedBy = usedByIds;
158
+ }
159
+
160
+ // AC3: Populate dependencies via analyzeDependencies (JS/TS files only)
161
+ const deps = await this._findDependencies(client, sourcePath);
162
+ if (deps !== null) {
163
+ data.dependencies = deps;
164
+ }
165
+
166
+ // AC4: Populate codeIntelMetadata
167
+ const callerCount = Array.isArray(data.usedBy) ? data.usedBy.length : 0;
168
+ data.codeIntelMetadata = {
169
+ callerCount,
170
+ role: inferRole(sourcePath),
171
+ lastSynced: now,
172
+ provider: client._activeProvider ? client._activeProvider.name : 'unknown',
173
+ };
174
+
175
+ return true;
176
+ }
177
+
178
+ /**
179
+ * Check if provider is available.
180
+ * @returns {boolean}
181
+ * @private
182
+ */
183
+ _isProviderAvailable() {
184
+ if (this._client) {
185
+ return typeof this._client.findReferences === 'function';
186
+ }
187
+ return isCodeIntelAvailable();
188
+ }
189
+
190
+ /**
191
+ * Check if entity should be skipped in incremental mode.
192
+ * @param {Object} entityData
193
+ * @returns {boolean} true if should skip
194
+ * @private
195
+ */
196
+ _shouldSkipIncremental(entityData) {
197
+ const metadata = entityData.codeIntelMetadata;
198
+
199
+ // Entities without lastSynced are always processed (AC6)
200
+ if (!metadata || !metadata.lastSynced) {
201
+ return false;
202
+ }
203
+
204
+ // Get file mtime
205
+ const sourcePath = entityData.path;
206
+ if (!sourcePath) return true;
207
+
208
+ const fullPath = path.resolve(this._repoRoot, sourcePath);
209
+ try {
210
+ const stat = fs.statSync(fullPath);
211
+ const lastSyncedMs = new Date(metadata.lastSynced).getTime();
212
+ // Skip if file hasn't changed since last sync
213
+ return stat.mtimeMs <= lastSyncedMs;
214
+ } catch (_error) {
215
+ // File doesn't exist — clear metadata and skip
216
+ this._logger(`[registry-syncer] Warning: source file not found for ${sourcePath}, skipping`);
217
+ return true;
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Find entities that reference this entity (usedBy).
223
+ * @param {Object} client - Code intel client
224
+ * @param {string} entityId - Entity ID to search for
225
+ * @param {Object} entities - Full entities map for cross-referencing
226
+ * @returns {Promise<string[]|null>} Array of entity IDs or null on failure
227
+ * @private
228
+ */
229
+ async _findUsedBy(client, entityId, entities) {
230
+ try {
231
+ const references = await client.findReferences(entityId);
232
+ if (!references || !Array.isArray(references)) return null;
233
+
234
+ // Cross-reference with registry to get entity IDs
235
+ const usedByIds = [];
236
+ for (const ref of references) {
237
+ const refPath = ref.file || ref.path || ref;
238
+ if (typeof refPath !== 'string') continue;
239
+
240
+ const matchedId = this._findEntityByPath(refPath, entities);
241
+ if (matchedId && matchedId !== entityId) {
242
+ usedByIds.push(matchedId);
243
+ }
244
+ }
245
+
246
+ return [...new Set(usedByIds)]; // Deduplicate
247
+ } catch (_error) {
248
+ return null;
249
+ }
250
+ }
251
+
252
+ /**
253
+ * Find dependencies of a source file.
254
+ * @param {Object} client - Code intel client
255
+ * @param {string} sourcePath - Source file path
256
+ * @returns {Promise<string[]|null>} Array of dependency names or null on failure
257
+ * @private
258
+ */
259
+ async _findDependencies(client, sourcePath) {
260
+ // Only analyze JS/TS files for import dependencies
261
+ if (!sourcePath.match(/\.(js|ts|mjs|cjs)$/)) return null;
262
+
263
+ try {
264
+ const fullPath = path.resolve(this._repoRoot, sourcePath);
265
+ const result = await client.analyzeDependencies(fullPath);
266
+ if (!result) return null;
267
+
268
+ // Filter to internal project dependencies only
269
+ const deps = [];
270
+ const items = result.dependencies || result.imports || result;
271
+ if (!Array.isArray(items)) return null;
272
+
273
+ for (const dep of items) {
274
+ const depPath = dep.path || dep.source || dep;
275
+ if (typeof depPath !== 'string') continue;
276
+ // Internal deps: relative paths or project paths (not node_modules)
277
+ if (depPath.startsWith('.') || depPath.startsWith('/') || depPath.includes('.aios-core')) {
278
+ deps.push(depPath);
279
+ }
280
+ }
281
+
282
+ return deps;
283
+ } catch (_error) {
284
+ return null;
285
+ }
286
+ }
287
+
288
+ /**
289
+ * Find entity ID by file path (cross-reference lookup).
290
+ * @param {string} filePath - File path from reference
291
+ * @param {Object} entities - Full entities map
292
+ * @returns {string|null} Entity ID or null
293
+ * @private
294
+ */
295
+ _findEntityByPath(filePath, entities) {
296
+ const normalized = filePath.replace(/\\/g, '/');
297
+ for (const [_category, categoryEntities] of Object.entries(entities)) {
298
+ if (!categoryEntities || typeof categoryEntities !== 'object') continue;
299
+ for (const [entityId, entityData] of Object.entries(categoryEntities)) {
300
+ if (entityData.path && normalized.includes(entityData.path.replace(/\\/g, '/'))) {
301
+ return entityId;
302
+ }
303
+ }
304
+ }
305
+ return null;
306
+ }
307
+
308
+ /**
309
+ * Atomic write: write to temp file then rename.
310
+ * Prevents partial corruption if process crashes mid-write.
311
+ * @param {string} registryPath - Target path
312
+ * @param {Object} registry - Registry data to write
313
+ * @private
314
+ */
315
+ _atomicWrite(registryPath, registry) {
316
+ const tmpPath = registryPath + '.tmp';
317
+ const content = yaml.dump(registry, { lineWidth: 120, noRefs: true });
318
+ fs.writeFileSync(tmpPath, content, 'utf8');
319
+ fs.renameSync(tmpPath, registryPath);
320
+ }
321
+
322
+ /**
323
+ * Get sync statistics from last run.
324
+ * @returns {Object}
325
+ */
326
+ getStats() {
327
+ return { ...this._stats };
328
+ }
329
+ }
330
+
331
+ module.exports = { RegistrySyncer, inferRole, ROLE_MAP };
@@ -232,6 +232,35 @@ class RegistryLoader {
232
232
  return entity;
233
233
  }
234
234
 
235
+ /**
236
+ * Get entity with code intelligence metadata (Story NOG-2).
237
+ * @param {string} entityId - Entity ID
238
+ * @returns {Object|null} Entity with codeIntelMetadata or null
239
+ */
240
+ getEntityWithIntel(entityId) {
241
+ const entity = this._findById(entityId);
242
+ if (!entity) return null;
243
+ return {
244
+ ...entity,
245
+ codeIntelMetadata: entity.codeIntelMetadata || null,
246
+ };
247
+ }
248
+
249
+ /**
250
+ * Query entities by keywords with optional role filter (Story NOG-2).
251
+ * @param {string[]} keywords - Keywords to search
252
+ * @param {Object} [options]
253
+ * @param {string} [options.role] - Filter by codeIntelMetadata.role
254
+ * @returns {Object[]} Matching entities
255
+ */
256
+ findByKeyword(keywords, options = {}) {
257
+ const results = this.queryByKeywords(keywords);
258
+ if (!options.role) return results;
259
+ return results.filter(
260
+ (e) => e.codeIntelMetadata && e.codeIntelMetadata.role === options.role,
261
+ );
262
+ }
263
+
235
264
  /**
236
265
  * Get registry metadata.
237
266
  */
@@ -6,7 +6,7 @@ const fs = require('fs');
6
6
  /**
7
7
  * Resolve runtime dependencies for Synapse hook execution.
8
8
  *
9
- * @param {{cwd?: string, sessionId?: string}} input
9
+ * @param {{cwd?: string, session_id?: string, sessionId?: string}} input
10
10
  * @returns {{
11
11
  * engine: import('../engine').SynapseEngine,
12
12
  * session: Object
@@ -14,7 +14,7 @@ const fs = require('fs');
14
14
  */
15
15
  function resolveHookRuntime(input) {
16
16
  const cwd = input && input.cwd;
17
- const sessionId = input && input.sessionId;
17
+ const sessionId = input && (input.session_id || input.sessionId);
18
18
  if (!cwd || typeof cwd !== 'string') return null;
19
19
 
20
20
  const synapsePath = path.join(cwd, '.synapse');
@@ -1,7 +1,7 @@
1
1
  metadata:
2
2
  version: 1.0.0
3
- lastUpdated: '2026-02-16T04:49:07.416Z'
4
- entityCount: 506
3
+ lastUpdated: '2026-02-16T20:22:35.905Z'
4
+ entityCount: 508
5
5
  checksumAlgorithm: sha256
6
6
  entities:
7
7
  tasks:
@@ -3909,6 +3909,24 @@ entities:
3909
3909
  extensionPoints: []
3910
3910
  checksum: sha256:f7a0bb8fed5663c88ad691b8871fdf7a861b6a7c02599f0c2db3eb9393d353c8
3911
3911
  lastVerified: '2026-02-16T01:21:26.585Z'
3912
+ sync-registry-intel:
3913
+ path: .aios-core/development/tasks/sync-registry-intel.md
3914
+ type: task
3915
+ purpose: 'Task: Sync Registry Intel'
3916
+ keywords:
3917
+ - sync
3918
+ - registry
3919
+ - intel
3920
+ - 'task:'
3921
+ usedBy: []
3922
+ dependencies:
3923
+ - registry-syncer
3924
+ adaptability:
3925
+ score: 0.8
3926
+ constraints: []
3927
+ extensionPoints: []
3928
+ checksum: sha256:0e69435307db814563823896e7ba9b29a4a9c10d90f6dedec5cb7a6d6f7ba936
3929
+ lastVerified: '2026-02-16T20:19:27.659Z'
3912
3930
  templates:
3913
3931
  activation-instructions-inline-greeting:
3914
3932
  path: .aios-core/product/templates/activation-instructions-inline-greeting.yaml
@@ -5979,6 +5997,24 @@ entities:
5979
5997
  checksum: sha256:310884d94b81be976a346987822306a16a73ba812c08c3b805f4a03216ffef38
5980
5998
  lastVerified: '2026-02-15T19:28:17.743Z'
5981
5999
  modules:
6000
+ registry-syncer:
6001
+ path: .aios-core/core/code-intel/registry-syncer.js
6002
+ type: module
6003
+ purpose: Entity at .aios-core\core\code-intel\registry-syncer.js
6004
+ keywords:
6005
+ - registry
6006
+ - syncer
6007
+ usedBy:
6008
+ - sync-registry-intel
6009
+ dependencies:
6010
+ - code-intel
6011
+ - registry-loader
6012
+ adaptability:
6013
+ score: 0.4
6014
+ constraints: []
6015
+ extensionPoints: []
6016
+ checksum: sha256:011318e2ba5c250daae2e565a8e8fb1570c99b7569e631276a2bf46e887fc514
6017
+ lastVerified: '2026-02-16T20:19:27.658Z'
5982
6018
  index.esm:
5983
6019
  path: .aios-core/core/index.esm.js
5984
6020
  type: module
@@ -6562,6 +6598,7 @@ entities:
6562
6598
  - registry
6563
6599
  - loader
6564
6600
  usedBy:
6601
+ - registry-syncer
6565
6602
  - index
6566
6603
  - framework-governor
6567
6604
  dependencies: []
@@ -6569,8 +6606,8 @@ entities:
6569
6606
  score: 0.4
6570
6607
  constraints: []
6571
6608
  extensionPoints: []
6572
- checksum: sha256:7fb2edb5a605765757d5ca7201025a7ff4cf7bfa9b18f35a311e2b658532a865
6573
- lastVerified: '2026-02-08T13:33:24.336Z'
6609
+ checksum: sha256:88c67bace0a5ab6a14cb1d096e3f9cab9f5edc0dd8377788e27b692ccefbd487
6610
+ lastVerified: '2026-02-16T20:19:27.659Z'
6574
6611
  manifest-generator:
6575
6612
  path: .aios-core/core/manifest/manifest-generator.js
6576
6613
  type: module
@@ -8536,7 +8573,7 @@ entities:
8536
8573
  hook-runtime:
8537
8574
  path: .aios-core/core/synapse/runtime/hook-runtime.js
8538
8575
  type: module
8539
- purpose: Entity at .aios-core/core/synapse/runtime/hook-runtime.js
8576
+ purpose: Entity at .aios-core\core\synapse\runtime\hook-runtime.js
8540
8577
  keywords:
8541
8578
  - hook
8542
8579
  - runtime
@@ -8546,8 +8583,8 @@ entities:
8546
8583
  score: 0.4
8547
8584
  constraints: []
8548
8585
  extensionPoints: []
8549
- checksum: sha256:441bdee8bbfea448f4e20eeb0ff1277352b33e79c9cf6cde9012c6d150fee08b
8550
- lastVerified: '2026-02-15T19:28:17.742Z'
8586
+ checksum: sha256:2fdd54f36a1bbb4ba05d6577c607cf2d6711dcf9a06284fc935676189a461fa2
8587
+ lastVerified: '2026-02-16T20:22:35.904Z'
8551
8588
  migration-config:
8552
8589
  path: .aios-core/core/migration/migration-config.yaml
8553
8590
  type: module
@@ -8819,8 +8856,8 @@ entities:
8819
8856
  score: 0.3
8820
8857
  constraints: []
8821
8858
  extensionPoints: []
8822
- checksum: sha256:538fff4398eba7c2492b446fce3a1d9115e5f1c6f494f0c3b45a991526c2295a
8823
- lastVerified: '2026-02-10T16:04:17.827Z'
8859
+ checksum: sha256:092161d318ab523b8cd5c3dc8a2bd19accc23ab7fa731d5b4fa11c5afb8b5a08
8860
+ lastVerified: '2026-02-16T19:52:18.846Z'
8824
8861
  analyst:
8825
8862
  path: .aios-core/development/agents/analyst.md
8826
8863
  type: agent
@@ -177,3 +177,15 @@
177
177
  {"timestamp":"2026-02-16T01:46:56.302Z","action":"change","path":".aios-core/core/code-intel/code-intel-enricher.js","trigger":"watcher"}
178
178
  {"timestamp":"2026-02-16T01:46:56.302Z","action":"change","path":".aios-core/core/code-intel/index.js","trigger":"watcher"}
179
179
  {"timestamp":"2026-02-16T01:46:56.303Z","action":"change","path":".aios-core/core/code-intel/providers/code-graph-provider.js","trigger":"watcher"}
180
+ {"timestamp":"2026-02-16T19:52:18.842Z","action":"add","path":".aios-core/core/code-intel/registry-syncer.js","trigger":"watcher"}
181
+ {"timestamp":"2026-02-16T19:52:18.845Z","action":"change","path":".aios-core/core/ids/registry-loader.js","trigger":"watcher"}
182
+ {"timestamp":"2026-02-16T19:52:18.846Z","action":"change","path":".aios-core/development/agents/aios-master.md","trigger":"watcher"}
183
+ {"timestamp":"2026-02-16T19:52:18.847Z","action":"add","path":".aios-core/development/tasks/sync-registry-intel.md","trigger":"watcher"}
184
+ {"timestamp":"2026-02-16T20:19:21.429Z","action":"change","path":".aios-core/core/code-intel/registry-syncer.js","trigger":"watcher"}
185
+ {"timestamp":"2026-02-16T20:19:21.430Z","action":"change","path":".aios-core/core/ids/registry-loader.js","trigger":"watcher"}
186
+ {"timestamp":"2026-02-16T20:19:21.431Z","action":"change","path":".aios-core/development/tasks/sync-registry-intel.md","trigger":"watcher"}
187
+ {"timestamp":"2026-02-16T20:19:27.658Z","action":"change","path":".aios-core/core/code-intel/registry-syncer.js","trigger":"watcher"}
188
+ {"timestamp":"2026-02-16T20:19:27.659Z","action":"change","path":".aios-core/core/ids/registry-loader.js","trigger":"watcher"}
189
+ {"timestamp":"2026-02-16T20:19:27.659Z","action":"change","path":".aios-core/development/tasks/sync-registry-intel.md","trigger":"watcher"}
190
+ {"timestamp":"2026-02-16T20:22:31.161Z","action":"change","path":".aios-core/core/synapse/runtime/hook-runtime.js","trigger":"watcher"}
191
+ {"timestamp":"2026-02-16T20:22:35.904Z","action":"change","path":".aios-core/core/synapse/runtime/hook-runtime.js","trigger":"watcher"}
@@ -217,6 +217,11 @@ commands:
217
217
  - name: ids stats
218
218
  description: 'Registry statistics (entity count by type, categories, health score)'
219
219
 
220
+ # Code Intelligence — Registry Enrichment (Story NOG-2)
221
+ - name: sync-registry-intel
222
+ args: '[--full]'
223
+ description: 'Enrich entity registry with code intelligence data (usedBy, dependencies, codeIntelMetadata). Use --full to force full resync.'
224
+
220
225
  # IDS Pre-Action Hooks (Story IDS-7)
221
226
  # These hooks run BEFORE *create and *modify commands as advisory (non-blocking) steps.
222
227
  ids_hooks:
@@ -282,6 +287,7 @@ dependencies:
282
287
  - run-workflow.md
283
288
  - run-workflow-engine.md
284
289
  - ids-governor.md
290
+ - sync-registry-intel.md
285
291
  # Delegated tasks (Story 6.1.2.3):
286
292
  # brownfield-create-epic.md → @pm
287
293
  # brownfield-create-story.md → @pm
@@ -0,0 +1,79 @@
1
+ # Task: Sync Registry Intel
2
+
3
+ ## Metadata
4
+ - **Task ID:** sync-registry-intel
5
+ - **Agent:** @aios-master
6
+ - **Story:** NOG-2
7
+ - **Type:** Command Task
8
+ - **Elicit:** false
9
+
10
+ ---
11
+
12
+ ## Description
13
+
14
+ Enrich the entity registry with code intelligence data (usedBy, dependencies, codeIntelMetadata) using the configured code intelligence provider.
15
+
16
+ ---
17
+
18
+ ## Prerequisites
19
+
20
+ - Code intelligence provider available (NOG-1 complete)
21
+ - Entity registry exists at `.aios-core/data/entity-registry.yaml`
22
+
23
+ ---
24
+
25
+ ## Execution Steps
26
+
27
+ ### Step 1: Parse Arguments
28
+
29
+ ```text
30
+ Arguments:
31
+ --full Force full resync (reprocess all entities regardless of lastSynced)
32
+
33
+ Default: Incremental sync (only entities whose source file mtime > lastSynced)
34
+ ```
35
+
36
+ ### Step 2: Execute Sync
37
+
38
+ ```javascript
39
+ const { RegistrySyncer } = require('.aios-core/core/code-intel/registry-syncer');
40
+
41
+ const syncer = new RegistrySyncer();
42
+ const stats = await syncer.sync({ full: hasFullFlag });
43
+ ```
44
+
45
+ ### Step 3: Report Results
46
+
47
+ Display sync statistics:
48
+ - Total entities in registry
49
+ - Entities processed (enriched)
50
+ - Entities skipped (unchanged)
51
+ - Errors encountered
52
+
53
+ ### Step 4: Handle Fallback
54
+
55
+ If no code intelligence provider is available:
56
+ - Display: "No code intelligence provider available, skipping enrichment"
57
+ - Exit gracefully with zero modifications
58
+
59
+ ---
60
+
61
+ ## Output
62
+
63
+ ```yaml
64
+ success: true
65
+ stats:
66
+ total: 506
67
+ processed: 42
68
+ skipped: 464
69
+ errors: 0
70
+ ```
71
+
72
+ ---
73
+
74
+ ## Error Handling
75
+
76
+ - **No provider:** Graceful exit, zero modifications
77
+ - **Registry not found:** Error message, exit
78
+ - **Partial failure:** Continue batch, log errors, report count
79
+ - **Write failure:** Atomic write prevents corruption (temp + rename)
@@ -7,10 +7,10 @@
7
7
  # - SHA256 hashes for change detection
8
8
  # - File types for categorization
9
9
  #
10
- version: 4.2.12
11
- generated_at: "2026-02-16T18:30:15.483Z"
10
+ version: 4.2.13
11
+ generated_at: "2026-02-16T21:09:06.284Z"
12
12
  generator: scripts/generate-install-manifest.js
13
- file_count: 1004
13
+ file_count: 1006
14
14
  files:
15
15
  - path: cli/commands/config/index.js
16
16
  hash: sha256:ebcad2ce3807eda29dcddff76d7a95ddc9b7fa160df21fd608f94b802237e862
@@ -200,6 +200,10 @@ files:
200
200
  hash: sha256:7d16aa715155e9c077720a6bffc7e9e5411b65f821b6b4e5e909f226796e7acb
201
201
  type: core
202
202
  size: 3079
203
+ - path: core/code-intel/registry-syncer.js
204
+ hash: sha256:011318e2ba5c250daae2e565a8e8fb1570c99b7569e631276a2bf46e887fc514
205
+ type: core
206
+ size: 10891
203
207
  - path: core/config/config-cache.js
204
208
  hash: sha256:527a788cbe650aa6b13d1101ebc16419489bfef20b2ee93042f6eb6a51e898e9
205
209
  type: core
@@ -565,9 +569,9 @@ files:
565
569
  type: core
566
570
  size: 26179
567
571
  - path: core/ids/registry-loader.js
568
- hash: sha256:e98cee3af79091e972b379786345d15ae451afbcba968c10fb3da9bf981bb363
572
+ hash: sha256:88c67bace0a5ab6a14cb1d096e3f9cab9f5edc0dd8377788e27b692ccefbd487
569
573
  type: core
570
- size: 7170
574
+ size: 8096
571
575
  - path: core/ids/registry-updater.js
572
576
  hash: sha256:6d87ec21d32acff1ba9b9d13025118c106ce6db59c1339c3a6ef4b2a02fd7f52
573
577
  type: core
@@ -985,9 +989,9 @@ files:
985
989
  type: core
986
990
  size: 16418
987
991
  - path: core/synapse/runtime/hook-runtime.js
988
- hash: sha256:441bdee8bbfea448f4e20eeb0ff1277352b33e79c9cf6cde9012c6d150fee08b
992
+ hash: sha256:2fdd54f36a1bbb4ba05d6577c607cf2d6711dcf9a06284fc935676189a461fa2
989
993
  type: core
990
- size: 1529
994
+ size: 1572
991
995
  - path: core/synapse/scripts/generate-constitution.js
992
996
  hash: sha256:65405d3e4ee080d19a25fb8967e159360a289e773c15253a351ee163b469e877
993
997
  type: script
@@ -1037,9 +1041,9 @@ files:
1037
1041
  type: data
1038
1042
  size: 34251
1039
1043
  - path: data/entity-registry.yaml
1040
- hash: sha256:7b9bb3a5354107f9ec29105b5948d5c538e2a3b19d49fa42e2e785e4a767c38b
1044
+ hash: sha256:fefd5d2eba3de2c49a3dbf6219cda466062b05a05b894dc2f89886b1f937bd21
1041
1045
  type: data
1042
- size: 289966
1046
+ size: 291072
1043
1047
  - path: data/learned-patterns.yaml
1044
1048
  hash: sha256:24ac0b160615583a0ff783d3da8af80b7f94191575d6db2054ec8e10a3f945dc
1045
1049
  type: data
@@ -1085,9 +1089,9 @@ files:
1085
1089
  type: development
1086
1090
  size: 5012
1087
1091
  - path: development/agents/aios-master.md
1088
- hash: sha256:538fff4398eba7c2492b446fce3a1d9115e5f1c6f494f0c3b45a991526c2295a
1092
+ hash: sha256:092161d318ab523b8cd5c3dc8a2bd19accc23ab7fa731d5b4fa11c5afb8b5a08
1089
1093
  type: agent
1090
- size: 17534
1094
+ size: 17821
1091
1095
  - path: development/agents/analyst.md
1092
1096
  hash: sha256:470384d9ee05d1373fe7519602f135179a88a35895252277823b35339dafd2a3
1093
1097
  type: agent
@@ -2180,6 +2184,10 @@ files:
2180
2184
  hash: sha256:caa2077e7a5bbbba9269b04e878b7772a71422ed6fd138447fe5cfb7345f96fb
2181
2185
  type: task
2182
2186
  size: 23362
2187
+ - path: development/tasks/sync-registry-intel.md
2188
+ hash: sha256:0e69435307db814563823896e7ba9b29a4a9c10d90f6dedec5cb7a6d6f7ba936
2189
+ type: task
2190
+ size: 1664
2183
2191
  - path: development/tasks/tailwind-upgrade.md
2184
2192
  hash: sha256:c369df0a28d8be7f0092405ecaed669a40075841427337990e2346b8c1d43c3a
2185
2193
  type: task
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aios-core",
3
- "version": "4.2.12",
3
+ "version": "4.2.13",
4
4
  "description": "Synkra AIOS: AI-Orchestrated System for Full Stack Development - Core Framework",
5
5
  "bin": {
6
6
  "aios": "bin/aios.js",
@@ -395,7 +395,34 @@ async function loginWithRetry(client, email) {
395
395
  // Activate Pro
396
396
  return activateProByAuth(client, loginResult.sessionToken);
397
397
  } catch (loginError) {
398
- if (loginError.code === 'INVALID_CREDENTIALS') {
398
+ if (loginError.code === 'EMAIL_NOT_VERIFIED') {
399
+ // Email not verified — poll by retrying login until verified
400
+ spinner.info('Email not verified yet. Please check your inbox and click the verification link.');
401
+ console.log(colors.dim(' (Checking every 5 seconds... timeout in 10 minutes)'));
402
+
403
+ const startTime = Date.now();
404
+ while (Date.now() - startTime < VERIFY_POLL_TIMEOUT_MS) {
405
+ await new Promise((resolve) => setTimeout(resolve, VERIFY_POLL_INTERVAL_MS));
406
+ try {
407
+ const retryLogin = await client.login(email, password);
408
+ showSuccess('Email verified!');
409
+ if (!retryLogin.emailVerified) {
410
+ const verifyResult = await waitForEmailVerification(client, retryLogin.sessionToken, email);
411
+ if (!verifyResult.success) return verifyResult;
412
+ }
413
+ return activateProByAuth(client, retryLogin.sessionToken);
414
+ } catch (retryError) {
415
+ if (retryError.code !== 'EMAIL_NOT_VERIFIED') {
416
+ return { success: false, error: retryError.message };
417
+ }
418
+ // Still not verified, continue polling
419
+ }
420
+ }
421
+
422
+ showError('Email verification timed out after 10 minutes.');
423
+ showInfo('Run the installer again to retry.');
424
+ return { success: false, error: 'Email verification timed out.' };
425
+ } else if (loginError.code === 'INVALID_CREDENTIALS') {
399
426
  const remaining = MAX_RETRIES - attempt;
400
427
  if (remaining > 0) {
401
428
  spinner.fail(`Incorrect password. ${remaining} attempt${remaining > 1 ? 's' : ''} remaining.`);
@@ -151,7 +151,24 @@ class LicenseApiClient {
151
151
  break;
152
152
 
153
153
  case 401:
154
- reject(LicenseActivationError.invalidKey());
154
+ // Preserve server error code (e.g., INVALID_CREDENTIALS, EMAIL_NOT_VERIFIED)
155
+ reject(
156
+ new LicenseActivationError(
157
+ message || 'Unauthorized',
158
+ code || 'INVALID_KEY',
159
+ details,
160
+ ),
161
+ );
162
+ break;
163
+
164
+ case 409:
165
+ reject(
166
+ new LicenseActivationError(
167
+ message || 'Conflict',
168
+ code || 'CONFLICT',
169
+ details,
170
+ ),
171
+ );
155
172
  break;
156
173
 
157
174
  case 403:
@@ -198,11 +215,12 @@ class LicenseApiClient {
198
215
  break;
199
216
 
200
217
  default:
218
+ // Preserve server error code/message when available
201
219
  reject(
202
220
  new LicenseActivationError(
203
- `Unexpected response: ${statusCode}`,
204
- 'UNEXPECTED_STATUS',
205
- { statusCode, response },
221
+ message || `Unexpected response: ${statusCode}`,
222
+ code || 'UNEXPECTED_STATUS',
223
+ details || { statusCode, response },
206
224
  ),
207
225
  );
208
226
  }
@@ -402,7 +420,10 @@ class LicenseApiClient {
402
420
  message: response.message || 'Verification email sent. Please check your inbox.',
403
421
  };
404
422
  } catch (error) {
405
- if (error.code === 'BAD_REQUEST' && error.message.includes('already')) {
423
+ if (
424
+ error.code === 'EMAIL_ALREADY_REGISTERED' ||
425
+ (error.code === 'BAD_REQUEST' && error.message.includes('already'))
426
+ ) {
406
427
  throw AuthError.emailAlreadyRegistered();
407
428
  }
408
429
  if (error.code === 'RATE_LIMITED') {
@@ -437,7 +458,14 @@ class LicenseApiClient {
437
458
  emailVerified: response.emailVerified !== false,
438
459
  };
439
460
  } catch (error) {
440
- if (error.code === 'INVALID_KEY' || error.code === 'BAD_REQUEST') {
461
+ if (error.code === 'EMAIL_NOT_VERIFIED') {
462
+ throw AuthError.emailNotVerified();
463
+ }
464
+ if (
465
+ error.code === 'INVALID_KEY' ||
466
+ error.code === 'INVALID_CREDENTIALS' ||
467
+ error.code === 'BAD_REQUEST'
468
+ ) {
441
469
  throw AuthError.invalidCredentials();
442
470
  }
443
471
  if (error.code === 'RATE_LIMITED') {