aios-core 4.2.12 → 4.2.14

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 (102) hide show
  1. package/.aios-core/core/code-intel/helpers/dev-helper.js +206 -0
  2. package/.aios-core/core/code-intel/registry-syncer.js +331 -0
  3. package/.aios-core/core/ids/registry-loader.js +29 -0
  4. package/.aios-core/core/registry/registry-schema.json +166 -166
  5. package/.aios-core/core/synapse/diagnostics/collectors/hook-collector.js +3 -3
  6. package/.aios-core/core/synapse/runtime/hook-runtime.js +2 -2
  7. package/.aios-core/data/entity-registry.yaml +73 -9
  8. package/.aios-core/development/agents/aios-master.md +6 -0
  9. package/.aios-core/development/scripts/approval-workflow.js +642 -642
  10. package/.aios-core/development/scripts/backup-manager.js +606 -606
  11. package/.aios-core/development/scripts/branch-manager.js +389 -389
  12. package/.aios-core/development/scripts/code-quality-improver.js +1311 -1311
  13. package/.aios-core/development/scripts/commit-message-generator.js +849 -849
  14. package/.aios-core/development/scripts/conflict-resolver.js +674 -674
  15. package/.aios-core/development/scripts/dependency-analyzer.js +637 -637
  16. package/.aios-core/development/scripts/diff-generator.js +351 -351
  17. package/.aios-core/development/scripts/elicitation-engine.js +384 -384
  18. package/.aios-core/development/scripts/elicitation-session-manager.js +299 -299
  19. package/.aios-core/development/scripts/git-wrapper.js +461 -461
  20. package/.aios-core/development/scripts/manifest-preview.js +244 -244
  21. package/.aios-core/development/scripts/metrics-tracker.js +775 -775
  22. package/.aios-core/development/scripts/modification-validator.js +554 -554
  23. package/.aios-core/development/scripts/pattern-learner.js +1224 -1224
  24. package/.aios-core/development/scripts/performance-analyzer.js +757 -757
  25. package/.aios-core/development/scripts/refactoring-suggester.js +1138 -1138
  26. package/.aios-core/development/scripts/rollback-handler.js +530 -530
  27. package/.aios-core/development/scripts/security-checker.js +358 -358
  28. package/.aios-core/development/scripts/template-engine.js +239 -239
  29. package/.aios-core/development/scripts/template-validator.js +278 -278
  30. package/.aios-core/development/scripts/test-generator.js +843 -843
  31. package/.aios-core/development/scripts/transaction-manager.js +589 -589
  32. package/.aios-core/development/scripts/usage-tracker.js +673 -673
  33. package/.aios-core/development/scripts/validate-filenames.js +226 -226
  34. package/.aios-core/development/scripts/version-tracker.js +526 -526
  35. package/.aios-core/development/scripts/yaml-validator.js +396 -396
  36. package/.aios-core/development/tasks/build-autonomous.md +10 -4
  37. package/.aios-core/development/tasks/create-service.md +23 -0
  38. package/.aios-core/development/tasks/dev-develop-story.md +12 -6
  39. package/.aios-core/development/tasks/dev-suggest-refactoring.md +7 -1
  40. package/.aios-core/development/tasks/publish-npm.md +3 -3
  41. package/.aios-core/development/tasks/sync-registry-intel.md +79 -0
  42. package/.aios-core/hooks/unified/README.md +1 -1
  43. package/.aios-core/install-manifest.yaml +79 -67
  44. package/.aios-core/manifests/schema/manifest-schema.json +190 -190
  45. package/.aios-core/product/templates/component-react-tmpl.tsx +98 -98
  46. package/.aios-core/product/templates/engine/schemas/adr.schema.json +102 -102
  47. package/.aios-core/product/templates/engine/schemas/dbdr.schema.json +205 -205
  48. package/.aios-core/product/templates/engine/schemas/epic.schema.json +175 -175
  49. package/.aios-core/product/templates/engine/schemas/pmdr.schema.json +175 -175
  50. package/.aios-core/product/templates/engine/schemas/prd-v2.schema.json +300 -300
  51. package/.aios-core/product/templates/engine/schemas/prd.schema.json +152 -152
  52. package/.aios-core/product/templates/engine/schemas/story.schema.json +222 -222
  53. package/.aios-core/product/templates/engine/schemas/task.schema.json +154 -154
  54. package/.aios-core/product/templates/eslintrc-security.json +32 -32
  55. package/.aios-core/product/templates/github-actions-cd.yml +212 -212
  56. package/.aios-core/product/templates/github-actions-ci.yml +172 -172
  57. package/.aios-core/product/templates/shock-report-tmpl.html +502 -502
  58. package/.aios-core/product/templates/token-exports-css-tmpl.css +240 -240
  59. package/.aios-core/quality/schemas/quality-metrics.schema.json +233 -233
  60. package/.aios-core/scripts/migrate-framework-docs.sh +300 -300
  61. package/README.en.md +747 -0
  62. package/README.md +4 -2
  63. package/bin/aios.js +7 -4
  64. package/package.json +1 -1
  65. package/packages/aios-pro-cli/src/recover.js +1 -1
  66. package/packages/installer/src/wizard/ide-config-generator.js +6 -6
  67. package/packages/installer/src/wizard/pro-setup.js +31 -4
  68. package/scripts/package-synapse.js +5 -5
  69. package/scripts/validate-package-completeness.js +3 -3
  70. package/.aios-core/.session/current-session.json +0 -14
  71. package/.aios-core/data/registry-update-log.jsonl +0 -179
  72. package/.aios-core/docs/SHARD-TRANSLATION-GUIDE.md +0 -335
  73. package/.aios-core/docs/component-creation-guide.md +0 -458
  74. package/.aios-core/docs/session-update-pattern.md +0 -307
  75. package/.aios-core/docs/standards/AIOS-FRAMEWORK-MASTER.md +0 -1963
  76. package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1-SUMMARY.md +0 -1190
  77. package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1.md +0 -439
  78. package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO.md +0 -5398
  79. package/.aios-core/docs/standards/V3-ARCHITECTURAL-DECISIONS.md +0 -523
  80. package/.aios-core/docs/template-syntax.md +0 -267
  81. package/.aios-core/docs/troubleshooting-guide.md +0 -625
  82. package/.aios-core/infrastructure/tests/utilities-audit-results.json +0 -501
  83. package/.aios-core/manifests/agents.csv +0 -29
  84. package/.aios-core/manifests/tasks.csv +0 -198
  85. package/.aios-core/manifests/workers.csv +0 -204
  86. package/.claude/rules/agent-authority.md +0 -105
  87. package/.claude/rules/coderabbit-integration.md +0 -93
  88. package/.claude/rules/ids-principles.md +0 -112
  89. package/.claude/rules/story-lifecycle.md +0 -139
  90. package/.claude/rules/workflow-execution.md +0 -150
  91. package/pro/README.md +0 -66
  92. package/pro/license/degradation.js +0 -220
  93. package/pro/license/errors.js +0 -450
  94. package/pro/license/feature-gate.js +0 -354
  95. package/pro/license/index.js +0 -181
  96. package/pro/license/license-api.js +0 -623
  97. package/pro/license/license-cache.js +0 -523
  98. package/pro/license/license-crypto.js +0 -303
  99. package/scripts/glue/README.md +0 -355
  100. package/scripts/glue/compose-agent-prompt.cjs +0 -362
  101. /package/.claude/hooks/{precompact-session-digest.js → precompact-session-digest.cjs} +0 -0
  102. /package/.claude/hooks/{synapse-engine.js → synapse-engine.cjs} +0 -0
@@ -0,0 +1,206 @@
1
+ 'use strict';
2
+
3
+ const { getEnricher, getClient, isCodeIntelAvailable } = require('../index');
4
+
5
+ // Risk level thresholds based on blast radius (reference count)
6
+ const RISK_THRESHOLDS = {
7
+ LOW_MAX: 4, // 0-4 refs = LOW
8
+ MEDIUM_MAX: 15, // 5-15 refs = MEDIUM
9
+ // >15 refs = HIGH
10
+ };
11
+
12
+ // Minimum references to suggest REUSE (>threshold = REUSE, <=threshold = ADAPT)
13
+ const REUSE_MIN_REFS = 2;
14
+
15
+ /**
16
+ * DevHelper — Code intelligence helper for @dev agent tasks.
17
+ *
18
+ * All functions return null gracefully when no provider is available.
19
+ * Never throws — safe to call unconditionally in task workflows.
20
+ */
21
+
22
+ /**
23
+ * Check for duplicates and similar code before writing a new file or function.
24
+ * Used by IDS Gate G4 in dev-develop-story and build-autonomous tasks.
25
+ *
26
+ * @param {string} fileName - Name of the file/function to be created
27
+ * @param {string} description - Description of what it does
28
+ * @returns {Promise<{duplicates: Object, references: Array, suggestion: string}|null>}
29
+ */
30
+ async function checkBeforeWriting(fileName, description) {
31
+ if (!isCodeIntelAvailable()) {
32
+ return null;
33
+ }
34
+
35
+ try {
36
+ const enricher = getEnricher();
37
+ const dupes = await enricher.detectDuplicates(description, { path: '.' });
38
+
39
+ // Also search for the fileName as a symbol reference
40
+ const client = getClient();
41
+ const nameRefs = await client.findReferences(fileName);
42
+
43
+ const hasMatches = (dupes && dupes.matches && dupes.matches.length > 0) ||
44
+ (nameRefs && nameRefs.length > 0);
45
+
46
+ if (!hasMatches) {
47
+ return null;
48
+ }
49
+
50
+ return {
51
+ duplicates: dupes,
52
+ references: nameRefs || [],
53
+ suggestion: _formatSuggestion(dupes, nameRefs),
54
+ };
55
+ } catch {
56
+ return null;
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Suggest reuse of an existing symbol instead of creating a new one.
62
+ * Searches for definitions and references to determine REUSE vs ADAPT.
63
+ *
64
+ * @param {string} symbol - Symbol name to search for
65
+ * @returns {Promise<{file: string, line: number, references: number, suggestion: string}|null>}
66
+ */
67
+ async function suggestReuse(symbol) {
68
+ if (!isCodeIntelAvailable()) {
69
+ return null;
70
+ }
71
+
72
+ try {
73
+ const client = getClient();
74
+ const [definition, refs] = await Promise.all([
75
+ client.findDefinition(symbol),
76
+ client.findReferences(symbol),
77
+ ]);
78
+
79
+ if (!definition && (!refs || refs.length === 0)) {
80
+ return null;
81
+ }
82
+
83
+ const refCount = refs ? refs.length : 0;
84
+ // REUSE if widely used, ADAPT if exists but lightly used
85
+ const suggestion = refCount > REUSE_MIN_REFS ? 'REUSE' : 'ADAPT';
86
+
87
+ return {
88
+ file: definition ? definition.file : (refs[0] ? refs[0].file : null),
89
+ line: definition ? definition.line : (refs[0] ? refs[0].line : null),
90
+ references: refCount,
91
+ suggestion,
92
+ };
93
+ } catch {
94
+ return null;
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Get naming conventions and patterns for a given path.
100
+ * Used to ensure new code follows existing project conventions.
101
+ *
102
+ * @param {string} targetPath - Path to analyze conventions for
103
+ * @returns {Promise<{patterns: Array, stats: Object}|null>}
104
+ */
105
+ async function getConventionsForPath(targetPath) {
106
+ if (!isCodeIntelAvailable()) {
107
+ return null;
108
+ }
109
+
110
+ try {
111
+ const enricher = getEnricher();
112
+ return await enricher.getConventions(targetPath);
113
+ } catch {
114
+ return null;
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Assess refactoring impact with blast radius and risk level.
120
+ * Used by dev-suggest-refactoring to show impact before changes.
121
+ *
122
+ * @param {string[]} files - Files to assess impact for
123
+ * @returns {Promise<{blastRadius: number, riskLevel: string, references: Array, complexity: Object}|null>}
124
+ */
125
+ async function assessRefactoringImpact(files) {
126
+ if (!isCodeIntelAvailable()) {
127
+ return null;
128
+ }
129
+
130
+ try {
131
+ const enricher = getEnricher();
132
+ const impact = await enricher.assessImpact(files);
133
+
134
+ if (!impact) {
135
+ return null;
136
+ }
137
+
138
+ return {
139
+ blastRadius: impact.blastRadius,
140
+ riskLevel: _calculateRiskLevel(impact.blastRadius),
141
+ references: impact.references,
142
+ complexity: impact.complexity,
143
+ };
144
+ } catch {
145
+ return null;
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Format a Code Intelligence Suggestion message from duplicate detection results.
151
+ * @param {Object|null} dupes - Result from detectDuplicates
152
+ * @param {Array|null} nameRefs - Result from findReferences
153
+ * @returns {string} Formatted suggestion message
154
+ * @private
155
+ */
156
+ function _formatSuggestion(dupes, nameRefs) {
157
+ const parts = [];
158
+
159
+ if (dupes && dupes.matches && dupes.matches.length > 0) {
160
+ parts.push(`Found ${dupes.matches.length} similar match(es) in codebase`);
161
+ const firstMatch = dupes.matches[0];
162
+ if (firstMatch.file) {
163
+ parts.push(`Closest: ${firstMatch.file}${firstMatch.line ? ':' + firstMatch.line : ''}`);
164
+ }
165
+ }
166
+
167
+ if (nameRefs && nameRefs.length > 0) {
168
+ parts.push(`Symbol already referenced in ${nameRefs.length} location(s)`);
169
+ const firstRef = nameRefs[0];
170
+ if (firstRef.file) {
171
+ parts.push(`First ref: ${firstRef.file}${firstRef.line ? ':' + firstRef.line : ''}`);
172
+ }
173
+ }
174
+
175
+ parts.push('Consider REUSE or ADAPT before creating new code (IDS Article IV-A)');
176
+
177
+ return parts.join('. ') + '.';
178
+ }
179
+
180
+ /**
181
+ * Calculate risk level from blast radius count.
182
+ * @param {number} blastRadius - Number of references affected
183
+ * @returns {string} 'LOW' | 'MEDIUM' | 'HIGH'
184
+ * @private
185
+ */
186
+ function _calculateRiskLevel(blastRadius) {
187
+ if (blastRadius <= RISK_THRESHOLDS.LOW_MAX) {
188
+ return 'LOW';
189
+ }
190
+ if (blastRadius <= RISK_THRESHOLDS.MEDIUM_MAX) {
191
+ return 'MEDIUM';
192
+ }
193
+ return 'HIGH';
194
+ }
195
+
196
+ module.exports = {
197
+ checkBeforeWriting,
198
+ suggestReuse,
199
+ getConventionsForPath,
200
+ assessRefactoringImpact,
201
+ // Exposed for testing
202
+ _formatSuggestion,
203
+ _calculateRiskLevel,
204
+ RISK_THRESHOLDS,
205
+ REUSE_MIN_REFS,
206
+ };
@@ -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
  */