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.
- package/.aios-core/core/code-intel/helpers/dev-helper.js +206 -0
- package/.aios-core/core/code-intel/registry-syncer.js +331 -0
- package/.aios-core/core/ids/registry-loader.js +29 -0
- package/.aios-core/core/registry/registry-schema.json +166 -166
- package/.aios-core/core/synapse/diagnostics/collectors/hook-collector.js +3 -3
- package/.aios-core/core/synapse/runtime/hook-runtime.js +2 -2
- package/.aios-core/data/entity-registry.yaml +73 -9
- package/.aios-core/development/agents/aios-master.md +6 -0
- package/.aios-core/development/scripts/approval-workflow.js +642 -642
- package/.aios-core/development/scripts/backup-manager.js +606 -606
- package/.aios-core/development/scripts/branch-manager.js +389 -389
- package/.aios-core/development/scripts/code-quality-improver.js +1311 -1311
- package/.aios-core/development/scripts/commit-message-generator.js +849 -849
- package/.aios-core/development/scripts/conflict-resolver.js +674 -674
- package/.aios-core/development/scripts/dependency-analyzer.js +637 -637
- package/.aios-core/development/scripts/diff-generator.js +351 -351
- package/.aios-core/development/scripts/elicitation-engine.js +384 -384
- package/.aios-core/development/scripts/elicitation-session-manager.js +299 -299
- package/.aios-core/development/scripts/git-wrapper.js +461 -461
- package/.aios-core/development/scripts/manifest-preview.js +244 -244
- package/.aios-core/development/scripts/metrics-tracker.js +775 -775
- package/.aios-core/development/scripts/modification-validator.js +554 -554
- package/.aios-core/development/scripts/pattern-learner.js +1224 -1224
- package/.aios-core/development/scripts/performance-analyzer.js +757 -757
- package/.aios-core/development/scripts/refactoring-suggester.js +1138 -1138
- package/.aios-core/development/scripts/rollback-handler.js +530 -530
- package/.aios-core/development/scripts/security-checker.js +358 -358
- package/.aios-core/development/scripts/template-engine.js +239 -239
- package/.aios-core/development/scripts/template-validator.js +278 -278
- package/.aios-core/development/scripts/test-generator.js +843 -843
- package/.aios-core/development/scripts/transaction-manager.js +589 -589
- package/.aios-core/development/scripts/usage-tracker.js +673 -673
- package/.aios-core/development/scripts/validate-filenames.js +226 -226
- package/.aios-core/development/scripts/version-tracker.js +526 -526
- package/.aios-core/development/scripts/yaml-validator.js +396 -396
- package/.aios-core/development/tasks/build-autonomous.md +10 -4
- package/.aios-core/development/tasks/create-service.md +23 -0
- package/.aios-core/development/tasks/dev-develop-story.md +12 -6
- package/.aios-core/development/tasks/dev-suggest-refactoring.md +7 -1
- package/.aios-core/development/tasks/publish-npm.md +3 -3
- package/.aios-core/development/tasks/sync-registry-intel.md +79 -0
- package/.aios-core/hooks/unified/README.md +1 -1
- package/.aios-core/install-manifest.yaml +79 -67
- package/.aios-core/manifests/schema/manifest-schema.json +190 -190
- package/.aios-core/product/templates/component-react-tmpl.tsx +98 -98
- package/.aios-core/product/templates/engine/schemas/adr.schema.json +102 -102
- package/.aios-core/product/templates/engine/schemas/dbdr.schema.json +205 -205
- package/.aios-core/product/templates/engine/schemas/epic.schema.json +175 -175
- package/.aios-core/product/templates/engine/schemas/pmdr.schema.json +175 -175
- package/.aios-core/product/templates/engine/schemas/prd-v2.schema.json +300 -300
- package/.aios-core/product/templates/engine/schemas/prd.schema.json +152 -152
- package/.aios-core/product/templates/engine/schemas/story.schema.json +222 -222
- package/.aios-core/product/templates/engine/schemas/task.schema.json +154 -154
- package/.aios-core/product/templates/eslintrc-security.json +32 -32
- package/.aios-core/product/templates/github-actions-cd.yml +212 -212
- package/.aios-core/product/templates/github-actions-ci.yml +172 -172
- package/.aios-core/product/templates/shock-report-tmpl.html +502 -502
- package/.aios-core/product/templates/token-exports-css-tmpl.css +240 -240
- package/.aios-core/quality/schemas/quality-metrics.schema.json +233 -233
- package/.aios-core/scripts/migrate-framework-docs.sh +300 -300
- package/README.en.md +747 -0
- package/README.md +4 -2
- package/bin/aios.js +7 -4
- package/package.json +1 -1
- package/packages/aios-pro-cli/src/recover.js +1 -1
- package/packages/installer/src/wizard/ide-config-generator.js +6 -6
- package/packages/installer/src/wizard/pro-setup.js +31 -4
- package/scripts/package-synapse.js +5 -5
- package/scripts/validate-package-completeness.js +3 -3
- package/.aios-core/.session/current-session.json +0 -14
- package/.aios-core/data/registry-update-log.jsonl +0 -179
- package/.aios-core/docs/SHARD-TRANSLATION-GUIDE.md +0 -335
- package/.aios-core/docs/component-creation-guide.md +0 -458
- package/.aios-core/docs/session-update-pattern.md +0 -307
- package/.aios-core/docs/standards/AIOS-FRAMEWORK-MASTER.md +0 -1963
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1-SUMMARY.md +0 -1190
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1.md +0 -439
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO.md +0 -5398
- package/.aios-core/docs/standards/V3-ARCHITECTURAL-DECISIONS.md +0 -523
- package/.aios-core/docs/template-syntax.md +0 -267
- package/.aios-core/docs/troubleshooting-guide.md +0 -625
- package/.aios-core/infrastructure/tests/utilities-audit-results.json +0 -501
- package/.aios-core/manifests/agents.csv +0 -29
- package/.aios-core/manifests/tasks.csv +0 -198
- package/.aios-core/manifests/workers.csv +0 -204
- package/.claude/rules/agent-authority.md +0 -105
- package/.claude/rules/coderabbit-integration.md +0 -93
- package/.claude/rules/ids-principles.md +0 -112
- package/.claude/rules/story-lifecycle.md +0 -139
- package/.claude/rules/workflow-execution.md +0 -150
- package/pro/README.md +0 -66
- package/pro/license/degradation.js +0 -220
- package/pro/license/errors.js +0 -450
- package/pro/license/feature-gate.js +0 -354
- package/pro/license/index.js +0 -181
- package/pro/license/license-api.js +0 -623
- package/pro/license/license-cache.js +0 -523
- package/pro/license/license-crypto.js +0 -303
- package/scripts/glue/README.md +0 -355
- package/scripts/glue/compose-agent-prompt.cjs +0 -362
- /package/.claude/hooks/{precompact-session-digest.js → precompact-session-digest.cjs} +0 -0
- /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
|
*/
|