principles-disciple 1.7.6 → 1.7.8

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 (106) hide show
  1. package/dist/commands/context.js +5 -15
  2. package/dist/commands/evolution-status.js +2 -9
  3. package/dist/commands/export.js +61 -8
  4. package/dist/commands/nocturnal-review.d.ts +24 -0
  5. package/dist/commands/nocturnal-review.js +265 -0
  6. package/dist/commands/nocturnal-rollout.d.ts +27 -0
  7. package/dist/commands/nocturnal-rollout.js +671 -0
  8. package/dist/commands/nocturnal-train.d.ts +25 -0
  9. package/dist/commands/nocturnal-train.js +919 -0
  10. package/dist/commands/pain.js +8 -21
  11. package/dist/constants/tools.d.ts +2 -2
  12. package/dist/constants/tools.js +1 -1
  13. package/dist/core/adaptive-thresholds.d.ts +186 -0
  14. package/dist/core/adaptive-thresholds.js +300 -0
  15. package/dist/core/config.d.ts +2 -38
  16. package/dist/core/config.js +6 -61
  17. package/dist/core/event-log.d.ts +1 -2
  18. package/dist/core/event-log.js +0 -3
  19. package/dist/core/evolution-engine.js +1 -21
  20. package/dist/core/evolution-reducer.d.ts +7 -1
  21. package/dist/core/evolution-reducer.js +56 -4
  22. package/dist/core/evolution-types.d.ts +61 -9
  23. package/dist/core/evolution-types.js +31 -9
  24. package/dist/core/external-training-contract.d.ts +276 -0
  25. package/dist/core/external-training-contract.js +269 -0
  26. package/dist/core/local-worker-routing.d.ts +175 -0
  27. package/dist/core/local-worker-routing.js +525 -0
  28. package/dist/core/model-deployment-registry.d.ts +218 -0
  29. package/dist/core/model-deployment-registry.js +503 -0
  30. package/dist/core/model-training-registry.d.ts +295 -0
  31. package/dist/core/model-training-registry.js +475 -0
  32. package/dist/core/nocturnal-arbiter.d.ts +159 -0
  33. package/dist/core/nocturnal-arbiter.js +534 -0
  34. package/dist/core/nocturnal-candidate-scoring.d.ts +137 -0
  35. package/dist/core/nocturnal-candidate-scoring.js +266 -0
  36. package/dist/core/nocturnal-compliance.d.ts +175 -0
  37. package/dist/core/nocturnal-compliance.js +824 -0
  38. package/dist/core/nocturnal-dataset.d.ts +224 -0
  39. package/dist/core/nocturnal-dataset.js +443 -0
  40. package/dist/core/nocturnal-executability.d.ts +85 -0
  41. package/dist/core/nocturnal-executability.js +331 -0
  42. package/dist/core/nocturnal-export.d.ts +124 -0
  43. package/dist/core/nocturnal-export.js +275 -0
  44. package/dist/core/nocturnal-paths.d.ts +124 -0
  45. package/dist/core/nocturnal-paths.js +214 -0
  46. package/dist/core/nocturnal-trajectory-extractor.d.ts +242 -0
  47. package/dist/core/nocturnal-trajectory-extractor.js +307 -0
  48. package/dist/core/nocturnal-trinity.d.ts +311 -0
  49. package/dist/core/nocturnal-trinity.js +880 -0
  50. package/dist/core/paths.d.ts +6 -0
  51. package/dist/core/paths.js +6 -0
  52. package/dist/core/principle-training-state.d.ts +121 -0
  53. package/dist/core/principle-training-state.js +321 -0
  54. package/dist/core/promotion-gate.d.ts +238 -0
  55. package/dist/core/promotion-gate.js +529 -0
  56. package/dist/core/session-tracker.d.ts +10 -0
  57. package/dist/core/session-tracker.js +14 -0
  58. package/dist/core/shadow-observation-registry.d.ts +217 -0
  59. package/dist/core/shadow-observation-registry.js +308 -0
  60. package/dist/core/training-program.d.ts +233 -0
  61. package/dist/core/training-program.js +433 -0
  62. package/dist/core/trajectory.d.ts +95 -1
  63. package/dist/core/trajectory.js +220 -6
  64. package/dist/core/workspace-context.d.ts +0 -6
  65. package/dist/core/workspace-context.js +0 -12
  66. package/dist/hooks/bash-risk.d.ts +6 -6
  67. package/dist/hooks/bash-risk.js +8 -8
  68. package/dist/hooks/gate-block-helper.js +1 -1
  69. package/dist/hooks/gate.d.ts +1 -1
  70. package/dist/hooks/gate.js +2 -2
  71. package/dist/hooks/gfi-gate.d.ts +3 -3
  72. package/dist/hooks/gfi-gate.js +15 -14
  73. package/dist/hooks/pain.js +6 -9
  74. package/dist/hooks/progressive-trust-gate.d.ts +21 -49
  75. package/dist/hooks/progressive-trust-gate.js +51 -204
  76. package/dist/hooks/prompt.d.ts +11 -11
  77. package/dist/hooks/prompt.js +158 -72
  78. package/dist/hooks/subagent.js +43 -6
  79. package/dist/i18n/commands.js +8 -8
  80. package/dist/index.js +129 -28
  81. package/dist/service/evolution-worker.d.ts +42 -4
  82. package/dist/service/evolution-worker.js +321 -13
  83. package/dist/service/nocturnal-runtime.d.ts +183 -0
  84. package/dist/service/nocturnal-runtime.js +352 -0
  85. package/dist/service/nocturnal-service.d.ts +163 -0
  86. package/dist/service/nocturnal-service.js +787 -0
  87. package/dist/service/nocturnal-target-selector.d.ts +145 -0
  88. package/dist/service/nocturnal-target-selector.js +315 -0
  89. package/dist/service/phase3-input-filter.d.ts +2 -23
  90. package/dist/service/phase3-input-filter.js +3 -27
  91. package/dist/service/runtime-summary-service.d.ts +0 -10
  92. package/dist/service/runtime-summary-service.js +1 -54
  93. package/dist/tools/deep-reflect.js +2 -1
  94. package/dist/types/event-types.d.ts +2 -10
  95. package/dist/types/runtime-summary.d.ts +1 -8
  96. package/dist/types.d.ts +0 -3
  97. package/dist/types.js +0 -2
  98. package/openclaw.plugin.json +1 -1
  99. package/package.json +1 -1
  100. package/templates/langs/en/skills/pd-mentor/SKILL.md +5 -5
  101. package/templates/langs/zh/skills/pd-mentor/SKILL.md +5 -5
  102. package/templates/pain_settings.json +0 -6
  103. package/dist/commands/trust.d.ts +0 -4
  104. package/dist/commands/trust.js +0 -78
  105. package/dist/core/trust-engine.d.ts +0 -96
  106. package/dist/core/trust-engine.js +0 -286
@@ -0,0 +1,275 @@
1
+ /**
2
+ * Nocturnal ORPO Export — Approved Dataset to Decision-Point JSONL
3
+ * =================================================================
4
+ *
5
+ * PURPOSE: Export approved nocturnal samples as ORPO-formatted decision-point
6
+ * training JSONL, strictly separated from legacy correction export.
7
+ *
8
+ * ARCHITECTURE:
9
+ * - Export output: .state/exports/orpo/{exportId}.jsonl
10
+ * - Export manifest: .state/exports/orpo/{exportId}-manifest.json
11
+ * - Legacy corrections: untouched, separate path
12
+ *
13
+ * ORPO FORMAT (each line):
14
+ * {
15
+ * sampleFingerprint: string,
16
+ * artifactId: string,
17
+ * sessionId: string,
18
+ * principleId: string,
19
+ * targetModelFamily: string,
20
+ * prompt: string, // badDecision (the wrong choice)
21
+ * chosen: string, // betterDecision (the right choice)
22
+ * rejected: string, // badDecision (for ORPO)
23
+ * rationale: string,
24
+ * datasetMetadata: {
25
+ * sampleFingerprint: string,
26
+ * artifactPath: string,
27
+ * createdAt: string,
28
+ * exportedAt: string,
29
+ * exportId: string,
30
+ * datasetFingerprint: string
31
+ * }
32
+ * }
33
+ *
34
+ * EXPORT GATING (fail-closed):
35
+ * - reviewStatus === 'approved_for_training'
36
+ * - targetModelFamily matches requested target (or any if not specified)
37
+ * - Lineage fields complete (sampleFingerprint, artifactId, sessionId, principleId)
38
+ * - Source artifact file exists and is approved
39
+ *
40
+ * DESIGN CONSTRAINTS:
41
+ * - No trainer invocation
42
+ * - No automatic training
43
+ * - No checkpoint deploy
44
+ * - Export is read-only from dataset perspective
45
+ */
46
+ import * as fs from 'fs';
47
+ import * as path from 'path';
48
+ import * as crypto from 'crypto';
49
+ import { listDatasetRecords, readDatasetArtifact, } from './nocturnal-dataset.js';
50
+ import { NocturnalPathResolver } from './nocturnal-paths.js';
51
+ // ---------------------------------------------------------------------------
52
+ // Dataset Fingerprint (for reproducibility)
53
+ // ---------------------------------------------------------------------------
54
+ /**
55
+ * Compute a deterministic dataset fingerprint from a sorted list of sample fingerprints.
56
+ * This allows reproducible exports — same dataset always produces same fingerprint.
57
+ */
58
+ function computeDatasetFingerprint(sampleFingerprints) {
59
+ const sorted = [...sampleFingerprints].sort();
60
+ const combined = sorted.join('|');
61
+ return crypto.createHash('sha256').update(combined, 'utf8').digest('hex');
62
+ }
63
+ // ---------------------------------------------------------------------------
64
+ // Individual Sample Serialization
65
+ // ---------------------------------------------------------------------------
66
+ /**
67
+ * Serialize a single dataset record + artifact to ORPO JSONL line.
68
+ * Caller guarantees record.targetModelFamily is non-null.
69
+ */
70
+ function serializeORPOSample(record, artifact, exportId, datasetFingerprint) {
71
+ const now = new Date().toISOString();
72
+ return {
73
+ sampleFingerprint: record.sampleFingerprint,
74
+ artifactId: record.artifactId,
75
+ sessionId: record.sessionId,
76
+ principleId: record.principleId,
77
+ targetModelFamily: record.targetModelFamily, // validated non-null by caller
78
+ // For ORPO: prompt = badDecision, chosen = betterDecision, rejected = badDecision
79
+ // This teaches the model to prefer betterDecision over badDecision
80
+ prompt: artifact.badDecision,
81
+ chosen: artifact.betterDecision,
82
+ rejected: artifact.badDecision,
83
+ rationale: artifact.rationale,
84
+ datasetMetadata: {
85
+ sampleFingerprint: record.sampleFingerprint,
86
+ artifactPath: record.artifactPath,
87
+ createdAt: record.createdAt,
88
+ exportedAt: now,
89
+ exportId,
90
+ datasetFingerprint,
91
+ },
92
+ };
93
+ }
94
+ // ---------------------------------------------------------------------------
95
+ // Core Export Function
96
+ // ---------------------------------------------------------------------------
97
+ /**
98
+ * Export approved nocturnal samples as ORPO decision-point JSONL.
99
+ *
100
+ * @param workspaceDir - Workspace directory
101
+ * @param targetModelFamily - Specific model family to export, or undefined for all
102
+ * @param options - Additional export options
103
+ * @returns ExportResult
104
+ */
105
+ export function exportORPOSamples(workspaceDir, targetModelFamily, _options = {}) {
106
+ const exportId = crypto.randomUUID();
107
+ const now = new Date().toISOString();
108
+ // Step 1: Collect eligible records
109
+ // Use listDatasetRecords directly to have full control over the family filter
110
+ // (listExportReadyRecords uses ?? which maps null→undefined, losing the null distinction)
111
+ const allApprovedRecords = listDatasetRecords(workspaceDir, {
112
+ reviewStatus: 'approved_for_training',
113
+ });
114
+ let eligibleRecords;
115
+ if (targetModelFamily !== undefined && targetModelFamily !== null) {
116
+ // Specific family: check if ANY records (regardless of status) have this family
117
+ const allRecords = listDatasetRecords(workspaceDir);
118
+ const hasAnyWithFamily = allRecords.some((r) => r.targetModelFamily === targetModelFamily);
119
+ if (!hasAnyWithFamily) {
120
+ // Family doesn't exist in any record
121
+ return {
122
+ success: false,
123
+ error: 'No samples found for the requested target model family',
124
+ emptyReason: 'family_mismatch',
125
+ };
126
+ }
127
+ // Family exists but none are approved
128
+ eligibleRecords = allApprovedRecords.filter((r) => r.targetModelFamily === targetModelFamily);
129
+ }
130
+ else {
131
+ // All families
132
+ eligibleRecords = allApprovedRecords;
133
+ }
134
+ // Step 2: Validate we have records
135
+ if (eligibleRecords.length === 0) {
136
+ return {
137
+ success: false,
138
+ error: 'No approved samples found for export',
139
+ emptyReason: 'no_approved_samples',
140
+ };
141
+ }
142
+ // Step 3: Verify lineage completeness and read artifacts
143
+ const orpoSamples = [];
144
+ const failedFingerprints = [];
145
+ for (const record of eligibleRecords) {
146
+ // Enforce targetModelFamily binding — samples without a family cannot enter training
147
+ if (record.targetModelFamily === null) {
148
+ failedFingerprints.push(record.sampleFingerprint);
149
+ continue;
150
+ }
151
+ // Verify lineage completeness
152
+ if (!record.sampleFingerprint || !record.artifactId || !record.sessionId || !record.principleId) {
153
+ failedFingerprints.push(record.sampleFingerprint);
154
+ continue;
155
+ }
156
+ // Read artifact (throws on error — distinguishes read failure from missing artifact)
157
+ let artifact;
158
+ try {
159
+ artifact = readDatasetArtifact(workspaceDir, record.sampleFingerprint);
160
+ }
161
+ catch {
162
+ failedFingerprints.push(record.sampleFingerprint);
163
+ continue;
164
+ }
165
+ // Serialize
166
+ orpoSamples.push(serializeORPOSample(record, artifact, exportId, ''));
167
+ }
168
+ // Step 4: Fail if all samples failed validation
169
+ if (orpoSamples.length === 0) {
170
+ return {
171
+ success: false,
172
+ error: `All ${eligibleRecords.length} eligible samples failed validation (missing artifacts or lineage)`,
173
+ emptyReason: 'all_samples_missing_artifacts',
174
+ };
175
+ }
176
+ // Step 5: Compute dataset fingerprint for manifest
177
+ const datasetFingerprint = computeDatasetFingerprint(orpoSamples.map((s) => s.sampleFingerprint));
178
+ // Step 6: Fill in dataset fingerprint in all samples
179
+ for (const sample of orpoSamples) {
180
+ sample.datasetMetadata.datasetFingerprint = datasetFingerprint;
181
+ }
182
+ // Step 7: Write JSONL file
183
+ const exportsDir = NocturnalPathResolver.exportsDir(workspaceDir);
184
+ const jsonlPath = path.join(exportsDir, `${exportId}.jsonl`);
185
+ const lines = orpoSamples.map((s) => JSON.stringify(s)).join('\n') + '\n';
186
+ fs.writeFileSync(jsonlPath, lines, 'utf-8');
187
+ // Step 8: Write manifest
188
+ const manifest = {
189
+ exportId,
190
+ createdAt: now,
191
+ sampleCount: orpoSamples.length,
192
+ targetModelFamily: targetModelFamily ?? 'all',
193
+ datasetFingerprint,
194
+ exportPath: jsonlPath,
195
+ manifestPath: path.join(exportsDir, `${exportId}-manifest.json`),
196
+ samples: orpoSamples.map((s) => ({
197
+ sampleFingerprint: s.sampleFingerprint,
198
+ artifactId: s.artifactId,
199
+ sessionId: s.sessionId,
200
+ principleId: s.principleId,
201
+ })),
202
+ };
203
+ fs.writeFileSync(manifest.manifestPath, JSON.stringify(manifest, null, 2), 'utf-8');
204
+ return {
205
+ success: true,
206
+ manifest,
207
+ };
208
+ }
209
+ /**
210
+ * Verify an existing export by re-computing its dataset fingerprint.
211
+ * Returns true if the export is intact and reproducible.
212
+ */
213
+ export function verifyExportIntegrity(workspaceDir, exportId) {
214
+ const exportsDir = NocturnalPathResolver.exportsDir(workspaceDir);
215
+ const manifestPath = path.join(exportsDir, `${exportId}-manifest.json`);
216
+ if (!fs.existsSync(manifestPath)) {
217
+ return null;
218
+ }
219
+ try {
220
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
221
+ const computedFingerprint = computeDatasetFingerprint(manifest.samples.map((s) => s.sampleFingerprint));
222
+ return {
223
+ valid: computedFingerprint === manifest.datasetFingerprint,
224
+ computedFingerprint,
225
+ manifestFingerprint: manifest.datasetFingerprint,
226
+ };
227
+ }
228
+ catch {
229
+ return null;
230
+ }
231
+ }
232
+ /**
233
+ * List all exports in the exports directory.
234
+ */
235
+ export function listExports(workspaceDir) {
236
+ const exportsDir = NocturnalPathResolver.exportsDir(workspaceDir);
237
+ if (!fs.existsSync(exportsDir)) {
238
+ return [];
239
+ }
240
+ try {
241
+ const files = fs.readdirSync(exportsDir);
242
+ const manifests = [];
243
+ for (const file of files) {
244
+ if (!file.endsWith('-manifest.json'))
245
+ continue;
246
+ try {
247
+ const manifest = JSON.parse(fs.readFileSync(path.join(exportsDir, file), 'utf-8'));
248
+ manifests.push(manifest);
249
+ }
250
+ catch {
251
+ // Skip malformed manifest
252
+ }
253
+ }
254
+ return manifests.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
255
+ }
256
+ catch {
257
+ return [];
258
+ }
259
+ }
260
+ /**
261
+ * Read an export manifest by ID.
262
+ */
263
+ export function getExportManifest(workspaceDir, exportId) {
264
+ const exportsDir = NocturnalPathResolver.exportsDir(workspaceDir);
265
+ const manifestPath = path.join(exportsDir, `${exportId}-manifest.json`);
266
+ if (!fs.existsSync(manifestPath)) {
267
+ return null;
268
+ }
269
+ try {
270
+ return JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
271
+ }
272
+ catch {
273
+ return null;
274
+ }
275
+ }
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Nocturnal Paths — Canonical Path Registry for Sleep-Mode Reflection Artifacts
3
+ * =============================================================================
4
+ *
5
+ * PURPOSE: Establishes a single source of truth for all nocturnal artifact paths.
6
+ * Prevents path fragmentation and ensures consistent resolution across modules.
7
+ *
8
+ * ARCHITECTURE:
9
+ * Operator-facing (read by agent, injected into prompts):
10
+ * memory/reflection-log.md ← human-readable lessons, NOT training data
11
+ *
12
+ * Nocturnal artifacts (structured, NOT injected into prompts):
13
+ * .state/nocturnal/samples/ ← decision-point JSON artifacts (Phase 2+)
14
+ * .state/nocturnal/memory/ ← short-term reflection memory (Phase 2+)
15
+ * .state/exports/orpo/ ← approved training pairs, immutable (Phase 3+)
16
+ *
17
+ * DESIGN CONSTRAINTS:
18
+ * - Nocturnal samples are NEVER written to memory/reflection-log.md
19
+ * - Prompt injection reads ONLY from memory/reflection-log.md
20
+ * - Each nocturnal artifact category has its own subdirectory
21
+ * - All paths go through this registry — no ad-hoc path construction
22
+ *
23
+ * USAGE:
24
+ * import { NocturnalPathResolver, NOCTURNAL_DIRS, NOCTURNAL_FILES } from './nocturnal-paths.js';
25
+ * const sampleDir = NocturnalPathResolver.samplesDir(workspaceDir);
26
+ */
27
+ export declare const NOCTURNAL_DIRS: {
28
+ /** Root directory for all nocturnal reflection artifacts */
29
+ readonly ROOT: ".state/nocturnal";
30
+ /**
31
+ * Structured decision-point samples from nocturnal reflection.
32
+ * Each file is a JSON artifact containing:
33
+ * - session snapshot reference
34
+ * - target principle
35
+ * - decision-point contrastive pair
36
+ * - arbiter approval status
37
+ */
38
+ readonly SAMPLES: ".state/nocturnal/samples";
39
+ /**
40
+ * Short-term operator-facing reflection memory.
41
+ * Written by nocturnal service, NOT injected into prompts directly.
42
+ * Consumed by operator on next session start.
43
+ */
44
+ readonly MEMORY: ".state/nocturnal/memory";
45
+ /**
46
+ * Nocturnal runtime bookkeeping (cooldowns, quotas).
47
+ * NOTE: nocturnal-runtime.json is written to stateDir directly
48
+ * (not under NOCTURNAL_DIRS.ROOT) for simpler migration.
49
+ */
50
+ /**
51
+ * Approved training pairs ready for export.
52
+ * Immutable once written — never modified in place.
53
+ * Consumed by external trainer (not the plugin).
54
+ */
55
+ readonly EXPORTS: ".state/exports/orpo";
56
+ };
57
+ export declare const NOCTURNAL_FILES: {
58
+ /**
59
+ * Arbiter review queue for pending samples.
60
+ * Not written by nocturnal service directly — created during arbiter review.
61
+ * Format: JSON array of sample refs pending approval.
62
+ */
63
+ readonly REVIEW_QUEUE: ".state/nocturnal/review-queue.json";
64
+ /**
65
+ * Lineage metadata for all samples.
66
+ * Written alongside each sample file for traceability.
67
+ */
68
+ readonly LINEAGE_INDEX: ".state/nocturnal/samples/lineage-index.json";
69
+ };
70
+ /**
71
+ * Resolves a nocturnal directory path within a workspace.
72
+ */
73
+ export declare function resolveNocturnalDir(workspaceDir: string, dirKey: keyof typeof NOCTURNAL_DIRS): string;
74
+ /**
75
+ * Resolves a nocturnal file path within a workspace.
76
+ */
77
+ export declare function resolveNocturnalFile(workspaceDir: string, fileKey: keyof typeof NOCTURNAL_FILES): string;
78
+ export declare const NocturnalPathResolver: {
79
+ /**
80
+ * Returns the samples directory path.
81
+ * Creates the directory if it does not exist.
82
+ */
83
+ readonly samplesDir: (workspaceDir: string) => string;
84
+ /**
85
+ * Returns the memory directory path.
86
+ * Creates the directory if it does not exist.
87
+ */
88
+ readonly memoryDir: (workspaceDir: string) => string;
89
+ /**
90
+ * Returns the exports directory path.
91
+ * Creates the directory if it does not exist.
92
+ */
93
+ readonly exportsDir: (workspaceDir: string) => string;
94
+ /**
95
+ * Returns the path for a named sample file.
96
+ * File is NOT created — caller decides when to write.
97
+ */
98
+ readonly samplePath: (workspaceDir: string, sampleId: string) => string;
99
+ /**
100
+ * Returns the path for nocturnal reflection memory.
101
+ * This is the operator-facing summary written after each nocturnal run.
102
+ */
103
+ readonly reflectionMemoryPath: (workspaceDir: string) => string;
104
+ /**
105
+ * Lists all sample files in the samples directory.
106
+ * Returns absolute paths sorted by modification time (newest first).
107
+ */
108
+ readonly listSamples: (workspaceDir: string) => string[];
109
+ /**
110
+ * Lists all approved sample files ready for export.
111
+ * Filters to samples with status === 'approved'.
112
+ */
113
+ readonly listApprovedSamples: (workspaceDir: string) => string[];
114
+ };
115
+ /**
116
+ * Complete path map for reference.
117
+ * These mirror the keys in NOCTURNAL_DIRS and NOCTURNAL_FILES.
118
+ */
119
+ export declare const NOCTURNAL_PATH_DESCRIPTIONS: Record<string, string>;
120
+ /**
121
+ * IMPORTANT: memory/reflection-log.md is NOT a nocturnal artifact.
122
+ * It is the pre-existing operator-facing reflection log, defined in paths.ts.
123
+ * It is kept separate from nocturnal outputs by design.
124
+ */
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Nocturnal Paths — Canonical Path Registry for Sleep-Mode Reflection Artifacts
3
+ * =============================================================================
4
+ *
5
+ * PURPOSE: Establishes a single source of truth for all nocturnal artifact paths.
6
+ * Prevents path fragmentation and ensures consistent resolution across modules.
7
+ *
8
+ * ARCHITECTURE:
9
+ * Operator-facing (read by agent, injected into prompts):
10
+ * memory/reflection-log.md ← human-readable lessons, NOT training data
11
+ *
12
+ * Nocturnal artifacts (structured, NOT injected into prompts):
13
+ * .state/nocturnal/samples/ ← decision-point JSON artifacts (Phase 2+)
14
+ * .state/nocturnal/memory/ ← short-term reflection memory (Phase 2+)
15
+ * .state/exports/orpo/ ← approved training pairs, immutable (Phase 3+)
16
+ *
17
+ * DESIGN CONSTRAINTS:
18
+ * - Nocturnal samples are NEVER written to memory/reflection-log.md
19
+ * - Prompt injection reads ONLY from memory/reflection-log.md
20
+ * - Each nocturnal artifact category has its own subdirectory
21
+ * - All paths go through this registry — no ad-hoc path construction
22
+ *
23
+ * USAGE:
24
+ * import { NocturnalPathResolver, NOCTURNAL_DIRS, NOCTURNAL_FILES } from './nocturnal-paths.js';
25
+ * const sampleDir = NocturnalPathResolver.samplesDir(workspaceDir);
26
+ */
27
+ import * as path from 'path';
28
+ import * as fs from 'fs';
29
+ // ---------------------------------------------------------------------------
30
+ // Directory Constants
31
+ // ---------------------------------------------------------------------------
32
+ export const NOCTURNAL_DIRS = {
33
+ /** Root directory for all nocturnal reflection artifacts */
34
+ ROOT: '.state/nocturnal',
35
+ /**
36
+ * Structured decision-point samples from nocturnal reflection.
37
+ * Each file is a JSON artifact containing:
38
+ * - session snapshot reference
39
+ * - target principle
40
+ * - decision-point contrastive pair
41
+ * - arbiter approval status
42
+ */
43
+ SAMPLES: '.state/nocturnal/samples',
44
+ /**
45
+ * Short-term operator-facing reflection memory.
46
+ * Written by nocturnal service, NOT injected into prompts directly.
47
+ * Consumed by operator on next session start.
48
+ */
49
+ MEMORY: '.state/nocturnal/memory',
50
+ /**
51
+ * Nocturnal runtime bookkeeping (cooldowns, quotas).
52
+ * NOTE: nocturnal-runtime.json is written to stateDir directly
53
+ * (not under NOCTURNAL_DIRS.ROOT) for simpler migration.
54
+ */
55
+ // RUNTIME is in {stateDir}/nocturnal-runtime.json (not here)
56
+ /**
57
+ * Approved training pairs ready for export.
58
+ * Immutable once written — never modified in place.
59
+ * Consumed by external trainer (not the plugin).
60
+ */
61
+ EXPORTS: '.state/exports/orpo',
62
+ };
63
+ // ---------------------------------------------------------------------------
64
+ // File Path Constants (within their respective directories)
65
+ // ---------------------------------------------------------------------------
66
+ export const NOCTURNAL_FILES = {
67
+ /**
68
+ * Arbiter review queue for pending samples.
69
+ * Not written by nocturnal service directly — created during arbiter review.
70
+ * Format: JSON array of sample refs pending approval.
71
+ */
72
+ REVIEW_QUEUE: '.state/nocturnal/review-queue.json',
73
+ /**
74
+ * Lineage metadata for all samples.
75
+ * Written alongside each sample file for traceability.
76
+ */
77
+ LINEAGE_INDEX: '.state/nocturnal/samples/lineage-index.json',
78
+ };
79
+ // ---------------------------------------------------------------------------
80
+ // Path Resolution
81
+ // ---------------------------------------------------------------------------
82
+ /**
83
+ * Cross-platform path join for workspace-relative paths.
84
+ * Handles Windows vs POSIX differences.
85
+ */
86
+ function joinWorkspacePath(workspaceDir, relativePath) {
87
+ const normalized = relativePath.replace(/\\/g, '/');
88
+ if (/^[A-Za-z]:/.test(workspaceDir)) {
89
+ // Windows
90
+ return path.win32.join(workspaceDir, ...normalized.split('/'));
91
+ }
92
+ return path.posix.join(workspaceDir, ...normalized.split('/'));
93
+ }
94
+ /**
95
+ * Resolves a nocturnal directory path within a workspace.
96
+ */
97
+ export function resolveNocturnalDir(workspaceDir, dirKey) {
98
+ return joinWorkspacePath(workspaceDir, NOCTURNAL_DIRS[dirKey]);
99
+ }
100
+ /**
101
+ * Resolves a nocturnal file path within a workspace.
102
+ */
103
+ export function resolveNocturnalFile(workspaceDir, fileKey) {
104
+ return joinWorkspacePath(workspaceDir, NOCTURNAL_FILES[fileKey]);
105
+ }
106
+ // ---------------------------------------------------------------------------
107
+ // NocturnalPathResolver — Fluent API for common resolutions
108
+ // ---------------------------------------------------------------------------
109
+ export const NocturnalPathResolver = {
110
+ /**
111
+ * Returns the samples directory path.
112
+ * Creates the directory if it does not exist.
113
+ */
114
+ samplesDir(workspaceDir) {
115
+ const dir = resolveNocturnalDir(workspaceDir, 'SAMPLES');
116
+ if (!fs.existsSync(dir)) {
117
+ fs.mkdirSync(dir, { recursive: true });
118
+ }
119
+ return dir;
120
+ },
121
+ /**
122
+ * Returns the memory directory path.
123
+ * Creates the directory if it does not exist.
124
+ */
125
+ memoryDir(workspaceDir) {
126
+ const dir = resolveNocturnalDir(workspaceDir, 'MEMORY');
127
+ if (!fs.existsSync(dir)) {
128
+ fs.mkdirSync(dir, { recursive: true });
129
+ }
130
+ return dir;
131
+ },
132
+ /**
133
+ * Returns the exports directory path.
134
+ * Creates the directory if it does not exist.
135
+ */
136
+ exportsDir(workspaceDir) {
137
+ const dir = resolveNocturnalDir(workspaceDir, 'EXPORTS');
138
+ if (!fs.existsSync(dir)) {
139
+ fs.mkdirSync(dir, { recursive: true });
140
+ }
141
+ return dir;
142
+ },
143
+ /**
144
+ * Returns the path for a named sample file.
145
+ * File is NOT created — caller decides when to write.
146
+ */
147
+ samplePath(workspaceDir, sampleId) {
148
+ const dir = resolveNocturnalDir(workspaceDir, 'SAMPLES');
149
+ // Sanitize sampleId for filesystem
150
+ const safeId = sampleId.replace(/[/\\:]/g, '_');
151
+ return path.join(dir, `${safeId}.json`);
152
+ },
153
+ /**
154
+ * Returns the path for nocturnal reflection memory.
155
+ * This is the operator-facing summary written after each nocturnal run.
156
+ */
157
+ reflectionMemoryPath(workspaceDir) {
158
+ return path.join(resolveNocturnalDir(workspaceDir, 'MEMORY'), 'reflection-memory.md');
159
+ },
160
+ /**
161
+ * Lists all sample files in the samples directory.
162
+ * Returns absolute paths sorted by modification time (newest first).
163
+ */
164
+ listSamples(workspaceDir) {
165
+ const dir = resolveNocturnalDir(workspaceDir, 'SAMPLES');
166
+ if (!fs.existsSync(dir)) {
167
+ return [];
168
+ }
169
+ try {
170
+ return fs.readdirSync(dir)
171
+ .filter(f => f.endsWith('.json'))
172
+ .map(f => path.join(dir, f))
173
+ .sort((a, b) => fs.statSync(b).mtime.getTime() - fs.statSync(a).mtime.getTime());
174
+ }
175
+ catch {
176
+ return [];
177
+ }
178
+ },
179
+ /**
180
+ * Lists all approved sample files ready for export.
181
+ * Filters to samples with status === 'approved'.
182
+ */
183
+ listApprovedSamples(workspaceDir) {
184
+ return this.listSamples(workspaceDir).filter(samplePath => {
185
+ try {
186
+ const content = fs.readFileSync(samplePath, 'utf-8');
187
+ const sample = JSON.parse(content);
188
+ return sample.status === 'approved';
189
+ }
190
+ catch {
191
+ return false;
192
+ }
193
+ });
194
+ },
195
+ };
196
+ // ---------------------------------------------------------------------------
197
+ // Constants for documentation / external reference
198
+ // ---------------------------------------------------------------------------
199
+ /**
200
+ * Complete path map for reference.
201
+ * These mirror the keys in NOCTURNAL_DIRS and NOCTURNAL_FILES.
202
+ */
203
+ export const NOCTURNAL_PATH_DESCRIPTIONS = {
204
+ '.state/nocturnal/samples/': 'Structured decision-point JSON artifacts (not injected into prompts)',
205
+ '.state/nocturnal/memory/': 'Short-term operator-facing reflection memory (not prompt-injected)',
206
+ '.state/exports/orpo/': 'Approved training pairs, immutable after export',
207
+ '.state/nocturnal/review-queue.json': 'Pending sample review queue',
208
+ 'memory/reflection-log.md': 'Operator-facing human-readable lessons (INJECTED into prompts)',
209
+ };
210
+ /**
211
+ * IMPORTANT: memory/reflection-log.md is NOT a nocturnal artifact.
212
+ * It is the pre-existing operator-facing reflection log, defined in paths.ts.
213
+ * It is kept separate from nocturnal outputs by design.
214
+ */