@nerviq/cli 1.18.0 → 1.19.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.
- package/LICENSE +23 -23
- package/README.md +2 -2
- package/bin/cli.js +130 -130
- package/package.json +1 -1
- package/src/activity.js +1039 -1039
- package/src/adoption-advisor.js +299 -299
- package/src/aider/config-parser.js +166 -166
- package/src/aider/context.js +158 -158
- package/src/aider/deep-review.js +316 -316
- package/src/aider/domain-packs.js +303 -303
- package/src/aider/freshness.js +93 -93
- package/src/aider/governance.js +253 -253
- package/src/aider/interactive.js +334 -334
- package/src/aider/mcp-packs.js +329 -329
- package/src/aider/patch.js +214 -214
- package/src/aider/plans.js +186 -186
- package/src/aider/premium.js +360 -360
- package/src/aider/setup.js +404 -404
- package/src/aider/techniques.js +16 -16
- package/src/analyze.js +951 -951
- package/src/anti-patterns.js +485 -485
- package/src/audit/instruction-files.js +180 -180
- package/src/audit/recommendations.js +577 -577
- package/src/auto-suggest.js +154 -154
- package/src/badge.js +13 -13
- package/src/behavioral-drift.js +801 -801
- package/src/benchmark.js +67 -67
- package/src/catalog.js +103 -103
- package/src/certification.js +128 -128
- package/src/codex/config-parser.js +183 -183
- package/src/codex/context.js +223 -223
- package/src/codex/deep-review.js +493 -493
- package/src/codex/domain-packs.js +394 -394
- package/src/codex/freshness.js +84 -84
- package/src/codex/governance.js +192 -192
- package/src/codex/interactive.js +618 -618
- package/src/codex/mcp-packs.js +914 -914
- package/src/codex/patch.js +209 -209
- package/src/codex/plans.js +251 -251
- package/src/codex/premium.js +614 -614
- package/src/codex/setup.js +591 -591
- package/src/context.js +320 -320
- package/src/continuous-ops.js +681 -681
- package/src/copilot/activity.js +309 -309
- package/src/copilot/deep-review.js +346 -346
- package/src/copilot/domain-packs.js +372 -372
- package/src/copilot/freshness.js +57 -57
- package/src/copilot/governance.js +222 -222
- package/src/copilot/interactive.js +406 -406
- package/src/copilot/mcp-packs.js +826 -826
- package/src/copilot/plans.js +253 -253
- package/src/copilot/premium.js +451 -451
- package/src/copilot/setup.js +488 -488
- package/src/cost-tracking.js +61 -61
- package/src/cursor/activity.js +301 -301
- package/src/cursor/config-parser.js +265 -265
- package/src/cursor/context.js +256 -256
- package/src/cursor/deep-review.js +334 -334
- package/src/cursor/domain-packs.js +368 -368
- package/src/cursor/freshness.js +65 -65
- package/src/cursor/governance.js +229 -229
- package/src/cursor/interactive.js +391 -391
- package/src/cursor/mcp-packs.js +828 -828
- package/src/cursor/plans.js +254 -254
- package/src/cursor/premium.js +469 -469
- package/src/cursor/setup.js +488 -488
- package/src/dashboard.js +493 -493
- package/src/deep-review.js +428 -428
- package/src/deprecation.js +98 -98
- package/src/diff-only.js +280 -280
- package/src/doctor.js +119 -119
- package/src/domain-pack-expansion.js +1033 -1033
- package/src/domain-packs.js +387 -387
- package/src/feedback.js +178 -178
- package/src/fix-engine.js +783 -783
- package/src/fix-prompts.js +122 -122
- package/src/formatters/sarif.js +115 -115
- package/src/freshness.js +74 -74
- package/src/gemini/config-parser.js +275 -275
- package/src/gemini/context.js +221 -221
- package/src/gemini/deep-review.js +559 -559
- package/src/gemini/domain-packs.js +393 -393
- package/src/gemini/freshness.js +66 -66
- package/src/gemini/governance.js +201 -201
- package/src/gemini/interactive.js +860 -860
- package/src/gemini/mcp-packs.js +915 -915
- package/src/gemini/plans.js +269 -269
- package/src/gemini/premium.js +760 -760
- package/src/gemini/setup.js +692 -692
- package/src/gemini/techniques.js +14 -14
- package/src/governance.js +72 -72
- package/src/harmony/add.js +68 -68
- package/src/harmony/advisor.js +333 -333
- package/src/harmony/canon.js +565 -565
- package/src/harmony/cli.js +591 -591
- package/src/harmony/drift.js +401 -401
- package/src/harmony/governance.js +313 -313
- package/src/harmony/memory.js +239 -239
- package/src/harmony/sync.js +475 -475
- package/src/harmony/watch.js +370 -370
- package/src/hook-validation.js +342 -342
- package/src/index.js +271 -271
- package/src/init.js +184 -184
- package/src/instruction-surfaces.js +185 -185
- package/src/integrations.js +144 -144
- package/src/interactive.js +118 -118
- package/src/locales/en.json +1 -1
- package/src/locales/es.json +1 -1
- package/src/mcp-packs.js +830 -830
- package/src/mcp-server.js +726 -726
- package/src/mcp-validation.js +337 -337
- package/src/nerviq-sync.json +7 -7
- package/src/opencode/config-parser.js +109 -109
- package/src/opencode/context.js +247 -247
- package/src/opencode/deep-review.js +313 -313
- package/src/opencode/domain-packs.js +262 -262
- package/src/opencode/freshness.js +66 -66
- package/src/opencode/governance.js +159 -159
- package/src/opencode/interactive.js +392 -392
- package/src/opencode/mcp-packs.js +705 -705
- package/src/opencode/patch.js +184 -184
- package/src/opencode/plans.js +231 -231
- package/src/opencode/premium.js +413 -413
- package/src/opencode/setup.js +449 -449
- package/src/opencode/techniques.js +27 -27
- package/src/operating-profile.js +574 -574
- package/src/org.js +152 -152
- package/src/permission-rules.js +218 -218
- package/src/plans.js +839 -839
- package/src/platform-change-manifest.js +86 -86
- package/src/plugins.js +110 -110
- package/src/policy-layers.js +210 -210
- package/src/profiles.js +124 -124
- package/src/prompt-injection.js +74 -74
- package/src/public-api.js +173 -173
- package/src/recommendation-rules.js +84 -84
- package/src/repo-archetype.js +386 -386
- package/src/secret-patterns.js +39 -39
- package/src/server.js +527 -527
- package/src/setup/analysis.js +607 -607
- package/src/setup/runtime.js +172 -172
- package/src/setup.js +677 -677
- package/src/shared/capabilities.js +194 -194
- package/src/source-urls.js +132 -132
- package/src/stack-checks.js +565 -565
- package/src/supplemental-checks.js +13 -13
- package/src/synergy/adaptive.js +261 -261
- package/src/synergy/compensation.js +137 -137
- package/src/synergy/evidence.js +193 -193
- package/src/synergy/learning.js +199 -199
- package/src/synergy/patterns.js +227 -227
- package/src/synergy/ranking.js +83 -83
- package/src/synergy/report.js +165 -165
- package/src/synergy/routing.js +146 -146
- package/src/techniques/api.js +407 -407
- package/src/techniques/automation.js +316 -316
- package/src/techniques/compliance.js +257 -257
- package/src/techniques/hygiene.js +294 -294
- package/src/techniques/instructions.js +243 -243
- package/src/techniques/observability.js +226 -226
- package/src/techniques/optimization.js +142 -142
- package/src/techniques/quality.js +318 -318
- package/src/techniques/security.js +237 -237
- package/src/techniques/shared.js +443 -443
- package/src/techniques/stacks.js +2294 -2294
- package/src/techniques/tools.js +106 -106
- package/src/techniques/workflow.js +413 -413
- package/src/techniques.js +81 -81
- package/src/terminology.js +73 -73
- package/src/token-estimate.js +35 -35
- package/src/usage-patterns.js +99 -99
- package/src/verification-metadata.js +145 -145
- package/src/watch.js +247 -247
- package/src/windsurf/activity.js +302 -302
- package/src/windsurf/config-parser.js +267 -267
- package/src/windsurf/context.js +249 -249
- package/src/windsurf/deep-review.js +337 -337
- package/src/windsurf/domain-packs.js +370 -370
- package/src/windsurf/freshness.js +36 -36
- package/src/windsurf/governance.js +231 -231
- package/src/windsurf/interactive.js +388 -388
- package/src/windsurf/mcp-packs.js +792 -792
- package/src/windsurf/plans.js +247 -247
- package/src/windsurf/premium.js +468 -468
- package/src/windsurf/setup.js +471 -471
- package/src/windsurf/techniques.js +17 -17
- package/src/workspace.js +375 -375
package/src/harmony/memory.js
CHANGED
|
@@ -1,239 +1,239 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* H7. Shared Memory / Knowledge Layer
|
|
3
|
-
*
|
|
4
|
-
* Cross-platform knowledge storage in .nerviq/harmony/ directory.
|
|
5
|
-
* Persists canonical models, drift history, platform scores,
|
|
6
|
-
* recommendation outcomes, and routing history.
|
|
7
|
-
*
|
|
8
|
-
* Zero external dependencies.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
const fs = require('fs');
|
|
12
|
-
const path = require('path');
|
|
13
|
-
const {
|
|
14
|
-
resolveHarmonyStateReadPath,
|
|
15
|
-
ensureHarmonyStateDir,
|
|
16
|
-
} = require('../state-paths');
|
|
17
|
-
|
|
18
|
-
const HARMONY_DIR = '.nerviq/harmony';
|
|
19
|
-
|
|
20
|
-
const STATE_FILES = {
|
|
21
|
-
canon: 'canon.json',
|
|
22
|
-
driftHistory: 'drift-history.json',
|
|
23
|
-
platformScores: 'platform-scores.json',
|
|
24
|
-
recommendationOutcomes: 'recommendation-outcomes.json',
|
|
25
|
-
routingHistory: 'routing-history.json',
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
// ─── File I/O helpers ─────────────────────────────────────────────────────────
|
|
29
|
-
|
|
30
|
-
function ensureHarmonyDir(dir) {
|
|
31
|
-
return ensureHarmonyStateDir(dir);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function readJsonSafe(filePath) {
|
|
35
|
-
try {
|
|
36
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
37
|
-
return JSON.parse(content);
|
|
38
|
-
} catch (_e) {
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function writeJsonAtomic(filePath, data) {
|
|
44
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
45
|
-
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf8');
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// ─── State management ─────────────────────────────────────────────────────────
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Save the full harmony state to disk.
|
|
52
|
-
*
|
|
53
|
-
* @param {string} dir - Project root directory
|
|
54
|
-
* @param {Object} state - State object with optional keys: canon, driftHistory, platformScores, recommendationOutcomes, routingHistory
|
|
55
|
-
*/
|
|
56
|
-
function saveHarmonyState(dir, state) {
|
|
57
|
-
const harmonyPath = ensureHarmonyDir(dir);
|
|
58
|
-
|
|
59
|
-
for (const [key, filename] of Object.entries(STATE_FILES)) {
|
|
60
|
-
if (state[key] !== undefined) {
|
|
61
|
-
const filePath = path.join(harmonyPath, filename);
|
|
62
|
-
writeJsonAtomic(filePath, state[key]);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Write a combined manifest
|
|
67
|
-
const manifest = {
|
|
68
|
-
lastUpdated: new Date().toISOString(),
|
|
69
|
-
files: Object.entries(STATE_FILES)
|
|
70
|
-
.filter(([key]) => state[key] !== undefined)
|
|
71
|
-
.map(([key, filename]) => ({ key, filename })),
|
|
72
|
-
};
|
|
73
|
-
writeJsonAtomic(path.join(harmonyPath, 'manifest.json'), manifest);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Load the full harmony state from disk.
|
|
78
|
-
*
|
|
79
|
-
* @param {string} dir - Project root directory
|
|
80
|
-
* @returns {Object} State object with all available keys populated
|
|
81
|
-
*/
|
|
82
|
-
function loadHarmonyState(dir) {
|
|
83
|
-
const state = {};
|
|
84
|
-
|
|
85
|
-
for (const [key, filename] of Object.entries(STATE_FILES)) {
|
|
86
|
-
const filePath = resolveHarmonyStateReadPath(dir, filename);
|
|
87
|
-
const data = readJsonSafe(filePath);
|
|
88
|
-
if (data !== null) {
|
|
89
|
-
state[key] = data;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Load manifest for metadata
|
|
94
|
-
const manifest = readJsonSafe(resolveHarmonyStateReadPath(dir, 'manifest.json'));
|
|
95
|
-
if (manifest) {
|
|
96
|
-
state._manifest = manifest;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return state;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// ─── History helpers ──────────────────────────────────────────────────────────
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Get the full harmony history with optional filtering.
|
|
106
|
-
*
|
|
107
|
-
* @param {string} dir - Project root
|
|
108
|
-
* @param {Object} [filter] - Optional filter: { platform, since, type }
|
|
109
|
-
* @returns {Object} { driftHistory, platformScores, routingHistory, recommendationOutcomes }
|
|
110
|
-
*/
|
|
111
|
-
function getHarmonyHistory(dir, filter) {
|
|
112
|
-
const state = loadHarmonyState(dir);
|
|
113
|
-
|
|
114
|
-
const result = {
|
|
115
|
-
driftHistory: state.driftHistory || [],
|
|
116
|
-
platformScores: state.platformScores || [],
|
|
117
|
-
routingHistory: state.routingHistory || [],
|
|
118
|
-
recommendationOutcomes: state.recommendationOutcomes || [],
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
if (!filter) return result;
|
|
122
|
-
|
|
123
|
-
const { platform, since, type } = filter;
|
|
124
|
-
|
|
125
|
-
const matchesFilter = (entry) => {
|
|
126
|
-
if (platform && entry.platform !== platform) return false;
|
|
127
|
-
if (since && entry.timestamp && entry.timestamp < since) return false;
|
|
128
|
-
if (type && entry.type !== type) return false;
|
|
129
|
-
return true;
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
if (platform || since || type) {
|
|
133
|
-
result.driftHistory = result.driftHistory.filter(matchesFilter);
|
|
134
|
-
result.platformScores = result.platformScores.filter(matchesFilter);
|
|
135
|
-
result.routingHistory = result.routingHistory.filter(matchesFilter);
|
|
136
|
-
result.recommendationOutcomes = result.recommendationOutcomes.filter(matchesFilter);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return result;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// ─── Append helpers (for incremental state updates) ───────────────────────────
|
|
143
|
-
|
|
144
|
-
function appendToArray(dir, stateKey, entry) {
|
|
145
|
-
const state = loadHarmonyState(dir);
|
|
146
|
-
const arr = Array.isArray(state[stateKey]) ? state[stateKey] : [];
|
|
147
|
-
arr.push({
|
|
148
|
-
...entry,
|
|
149
|
-
timestamp: entry.timestamp || new Date().toISOString(),
|
|
150
|
-
});
|
|
151
|
-
saveHarmonyState(dir, { ...state, [stateKey]: arr });
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Record a routing outcome (which platform was used for what, and how it went).
|
|
156
|
-
*
|
|
157
|
-
* @param {string} dir - Project root
|
|
158
|
-
* @param {Object} outcome
|
|
159
|
-
* @param {string} outcome.taskType - e.g. 'bug-fix', 'ci-review'
|
|
160
|
-
* @param {string} outcome.platform - e.g. 'claude', 'codex'
|
|
161
|
-
* @param {string} outcome.result - 'success' | 'partial' | 'failure'
|
|
162
|
-
* @param {string} [outcome.notes] - Free-form notes
|
|
163
|
-
*/
|
|
164
|
-
function recordRoutingOutcome(dir, outcome) {
|
|
165
|
-
appendToArray(dir, 'routingHistory', {
|
|
166
|
-
type: 'routing',
|
|
167
|
-
taskType: outcome.taskType,
|
|
168
|
-
platform: outcome.platform,
|
|
169
|
-
result: outcome.result,
|
|
170
|
-
notes: outcome.notes || '',
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Record a drift measurement.
|
|
176
|
-
*
|
|
177
|
-
* @param {string} dir - Project root
|
|
178
|
-
* @param {Object} drift
|
|
179
|
-
* @param {string} drift.platform - Platform that drifted
|
|
180
|
-
* @param {number} drift.driftScore - Measured drift value
|
|
181
|
-
* @param {Array} [drift.driftedFields] - Which fields drifted
|
|
182
|
-
*/
|
|
183
|
-
function recordDrift(dir, drift) {
|
|
184
|
-
appendToArray(dir, 'driftHistory', {
|
|
185
|
-
type: 'drift',
|
|
186
|
-
platform: drift.platform,
|
|
187
|
-
driftScore: drift.driftScore,
|
|
188
|
-
driftedFields: drift.driftedFields || [],
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Record a platform audit score snapshot.
|
|
194
|
-
*
|
|
195
|
-
* @param {string} dir - Project root
|
|
196
|
-
* @param {string} platform - Platform key
|
|
197
|
-
* @param {number} score - Audit score
|
|
198
|
-
* @param {Object} [details] - Additional audit details
|
|
199
|
-
*/
|
|
200
|
-
function recordPlatformScore(dir, platform, score, details) {
|
|
201
|
-
appendToArray(dir, 'platformScores', {
|
|
202
|
-
type: 'score',
|
|
203
|
-
platform,
|
|
204
|
-
score,
|
|
205
|
-
details: details || {},
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Record a recommendation outcome.
|
|
211
|
-
*
|
|
212
|
-
* @param {string} dir - Project root
|
|
213
|
-
* @param {Object} outcome
|
|
214
|
-
* @param {string} outcome.recommendation - The recommendation text
|
|
215
|
-
* @param {string} outcome.platform - Target platform
|
|
216
|
-
* @param {string} outcome.result - 'accepted' | 'rejected' | 'deferred'
|
|
217
|
-
* @param {string} [outcome.reason] - Why the outcome was chosen
|
|
218
|
-
*/
|
|
219
|
-
function recordRecommendationOutcome(dir, outcome) {
|
|
220
|
-
appendToArray(dir, 'recommendationOutcomes', {
|
|
221
|
-
type: 'recommendation',
|
|
222
|
-
recommendation: outcome.recommendation,
|
|
223
|
-
platform: outcome.platform,
|
|
224
|
-
result: outcome.result,
|
|
225
|
-
reason: outcome.reason || '',
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
module.exports = {
|
|
230
|
-
saveHarmonyState,
|
|
231
|
-
loadHarmonyState,
|
|
232
|
-
getHarmonyHistory,
|
|
233
|
-
recordRoutingOutcome,
|
|
234
|
-
recordDrift,
|
|
235
|
-
recordPlatformScore,
|
|
236
|
-
recordRecommendationOutcome,
|
|
237
|
-
HARMONY_DIR,
|
|
238
|
-
STATE_FILES,
|
|
239
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* H7. Shared Memory / Knowledge Layer
|
|
3
|
+
*
|
|
4
|
+
* Cross-platform knowledge storage in .nerviq/harmony/ directory.
|
|
5
|
+
* Persists canonical models, drift history, platform scores,
|
|
6
|
+
* recommendation outcomes, and routing history.
|
|
7
|
+
*
|
|
8
|
+
* Zero external dependencies.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const {
|
|
14
|
+
resolveHarmonyStateReadPath,
|
|
15
|
+
ensureHarmonyStateDir,
|
|
16
|
+
} = require('../state-paths');
|
|
17
|
+
|
|
18
|
+
const HARMONY_DIR = '.nerviq/harmony';
|
|
19
|
+
|
|
20
|
+
const STATE_FILES = {
|
|
21
|
+
canon: 'canon.json',
|
|
22
|
+
driftHistory: 'drift-history.json',
|
|
23
|
+
platformScores: 'platform-scores.json',
|
|
24
|
+
recommendationOutcomes: 'recommendation-outcomes.json',
|
|
25
|
+
routingHistory: 'routing-history.json',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// ─── File I/O helpers ─────────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
function ensureHarmonyDir(dir) {
|
|
31
|
+
return ensureHarmonyStateDir(dir);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function readJsonSafe(filePath) {
|
|
35
|
+
try {
|
|
36
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
37
|
+
return JSON.parse(content);
|
|
38
|
+
} catch (_e) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function writeJsonAtomic(filePath, data) {
|
|
44
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
45
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf8');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ─── State management ─────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Save the full harmony state to disk.
|
|
52
|
+
*
|
|
53
|
+
* @param {string} dir - Project root directory
|
|
54
|
+
* @param {Object} state - State object with optional keys: canon, driftHistory, platformScores, recommendationOutcomes, routingHistory
|
|
55
|
+
*/
|
|
56
|
+
function saveHarmonyState(dir, state) {
|
|
57
|
+
const harmonyPath = ensureHarmonyDir(dir);
|
|
58
|
+
|
|
59
|
+
for (const [key, filename] of Object.entries(STATE_FILES)) {
|
|
60
|
+
if (state[key] !== undefined) {
|
|
61
|
+
const filePath = path.join(harmonyPath, filename);
|
|
62
|
+
writeJsonAtomic(filePath, state[key]);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Write a combined manifest
|
|
67
|
+
const manifest = {
|
|
68
|
+
lastUpdated: new Date().toISOString(),
|
|
69
|
+
files: Object.entries(STATE_FILES)
|
|
70
|
+
.filter(([key]) => state[key] !== undefined)
|
|
71
|
+
.map(([key, filename]) => ({ key, filename })),
|
|
72
|
+
};
|
|
73
|
+
writeJsonAtomic(path.join(harmonyPath, 'manifest.json'), manifest);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Load the full harmony state from disk.
|
|
78
|
+
*
|
|
79
|
+
* @param {string} dir - Project root directory
|
|
80
|
+
* @returns {Object} State object with all available keys populated
|
|
81
|
+
*/
|
|
82
|
+
function loadHarmonyState(dir) {
|
|
83
|
+
const state = {};
|
|
84
|
+
|
|
85
|
+
for (const [key, filename] of Object.entries(STATE_FILES)) {
|
|
86
|
+
const filePath = resolveHarmonyStateReadPath(dir, filename);
|
|
87
|
+
const data = readJsonSafe(filePath);
|
|
88
|
+
if (data !== null) {
|
|
89
|
+
state[key] = data;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Load manifest for metadata
|
|
94
|
+
const manifest = readJsonSafe(resolveHarmonyStateReadPath(dir, 'manifest.json'));
|
|
95
|
+
if (manifest) {
|
|
96
|
+
state._manifest = manifest;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return state;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ─── History helpers ──────────────────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get the full harmony history with optional filtering.
|
|
106
|
+
*
|
|
107
|
+
* @param {string} dir - Project root
|
|
108
|
+
* @param {Object} [filter] - Optional filter: { platform, since, type }
|
|
109
|
+
* @returns {Object} { driftHistory, platformScores, routingHistory, recommendationOutcomes }
|
|
110
|
+
*/
|
|
111
|
+
function getHarmonyHistory(dir, filter) {
|
|
112
|
+
const state = loadHarmonyState(dir);
|
|
113
|
+
|
|
114
|
+
const result = {
|
|
115
|
+
driftHistory: state.driftHistory || [],
|
|
116
|
+
platformScores: state.platformScores || [],
|
|
117
|
+
routingHistory: state.routingHistory || [],
|
|
118
|
+
recommendationOutcomes: state.recommendationOutcomes || [],
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
if (!filter) return result;
|
|
122
|
+
|
|
123
|
+
const { platform, since, type } = filter;
|
|
124
|
+
|
|
125
|
+
const matchesFilter = (entry) => {
|
|
126
|
+
if (platform && entry.platform !== platform) return false;
|
|
127
|
+
if (since && entry.timestamp && entry.timestamp < since) return false;
|
|
128
|
+
if (type && entry.type !== type) return false;
|
|
129
|
+
return true;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
if (platform || since || type) {
|
|
133
|
+
result.driftHistory = result.driftHistory.filter(matchesFilter);
|
|
134
|
+
result.platformScores = result.platformScores.filter(matchesFilter);
|
|
135
|
+
result.routingHistory = result.routingHistory.filter(matchesFilter);
|
|
136
|
+
result.recommendationOutcomes = result.recommendationOutcomes.filter(matchesFilter);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ─── Append helpers (for incremental state updates) ───────────────────────────
|
|
143
|
+
|
|
144
|
+
function appendToArray(dir, stateKey, entry) {
|
|
145
|
+
const state = loadHarmonyState(dir);
|
|
146
|
+
const arr = Array.isArray(state[stateKey]) ? state[stateKey] : [];
|
|
147
|
+
arr.push({
|
|
148
|
+
...entry,
|
|
149
|
+
timestamp: entry.timestamp || new Date().toISOString(),
|
|
150
|
+
});
|
|
151
|
+
saveHarmonyState(dir, { ...state, [stateKey]: arr });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Record a routing outcome (which platform was used for what, and how it went).
|
|
156
|
+
*
|
|
157
|
+
* @param {string} dir - Project root
|
|
158
|
+
* @param {Object} outcome
|
|
159
|
+
* @param {string} outcome.taskType - e.g. 'bug-fix', 'ci-review'
|
|
160
|
+
* @param {string} outcome.platform - e.g. 'claude', 'codex'
|
|
161
|
+
* @param {string} outcome.result - 'success' | 'partial' | 'failure'
|
|
162
|
+
* @param {string} [outcome.notes] - Free-form notes
|
|
163
|
+
*/
|
|
164
|
+
function recordRoutingOutcome(dir, outcome) {
|
|
165
|
+
appendToArray(dir, 'routingHistory', {
|
|
166
|
+
type: 'routing',
|
|
167
|
+
taskType: outcome.taskType,
|
|
168
|
+
platform: outcome.platform,
|
|
169
|
+
result: outcome.result,
|
|
170
|
+
notes: outcome.notes || '',
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Record a drift measurement.
|
|
176
|
+
*
|
|
177
|
+
* @param {string} dir - Project root
|
|
178
|
+
* @param {Object} drift
|
|
179
|
+
* @param {string} drift.platform - Platform that drifted
|
|
180
|
+
* @param {number} drift.driftScore - Measured drift value
|
|
181
|
+
* @param {Array} [drift.driftedFields] - Which fields drifted
|
|
182
|
+
*/
|
|
183
|
+
function recordDrift(dir, drift) {
|
|
184
|
+
appendToArray(dir, 'driftHistory', {
|
|
185
|
+
type: 'drift',
|
|
186
|
+
platform: drift.platform,
|
|
187
|
+
driftScore: drift.driftScore,
|
|
188
|
+
driftedFields: drift.driftedFields || [],
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Record a platform audit score snapshot.
|
|
194
|
+
*
|
|
195
|
+
* @param {string} dir - Project root
|
|
196
|
+
* @param {string} platform - Platform key
|
|
197
|
+
* @param {number} score - Audit score
|
|
198
|
+
* @param {Object} [details] - Additional audit details
|
|
199
|
+
*/
|
|
200
|
+
function recordPlatformScore(dir, platform, score, details) {
|
|
201
|
+
appendToArray(dir, 'platformScores', {
|
|
202
|
+
type: 'score',
|
|
203
|
+
platform,
|
|
204
|
+
score,
|
|
205
|
+
details: details || {},
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Record a recommendation outcome.
|
|
211
|
+
*
|
|
212
|
+
* @param {string} dir - Project root
|
|
213
|
+
* @param {Object} outcome
|
|
214
|
+
* @param {string} outcome.recommendation - The recommendation text
|
|
215
|
+
* @param {string} outcome.platform - Target platform
|
|
216
|
+
* @param {string} outcome.result - 'accepted' | 'rejected' | 'deferred'
|
|
217
|
+
* @param {string} [outcome.reason] - Why the outcome was chosen
|
|
218
|
+
*/
|
|
219
|
+
function recordRecommendationOutcome(dir, outcome) {
|
|
220
|
+
appendToArray(dir, 'recommendationOutcomes', {
|
|
221
|
+
type: 'recommendation',
|
|
222
|
+
recommendation: outcome.recommendation,
|
|
223
|
+
platform: outcome.platform,
|
|
224
|
+
result: outcome.result,
|
|
225
|
+
reason: outcome.reason || '',
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
module.exports = {
|
|
230
|
+
saveHarmonyState,
|
|
231
|
+
loadHarmonyState,
|
|
232
|
+
getHarmonyHistory,
|
|
233
|
+
recordRoutingOutcome,
|
|
234
|
+
recordDrift,
|
|
235
|
+
recordPlatformScore,
|
|
236
|
+
recordRecommendationOutcome,
|
|
237
|
+
HARMONY_DIR,
|
|
238
|
+
STATE_FILES,
|
|
239
|
+
};
|