@ouro.bot/cli 0.0.1-alpha.0 → 0.1.0-alpha.2

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 (119) hide show
  1. package/AdoptionSpecialist.ouro/agent.json +20 -0
  2. package/AdoptionSpecialist.ouro/psyche/SOUL.md +22 -0
  3. package/AdoptionSpecialist.ouro/psyche/identities/basilisk.md +31 -0
  4. package/AdoptionSpecialist.ouro/psyche/identities/jafar.md +31 -0
  5. package/AdoptionSpecialist.ouro/psyche/identities/jormungandr.md +31 -0
  6. package/AdoptionSpecialist.ouro/psyche/identities/kaa.md +31 -0
  7. package/AdoptionSpecialist.ouro/psyche/identities/medusa.md +31 -0
  8. package/AdoptionSpecialist.ouro/psyche/identities/monty.md +31 -0
  9. package/AdoptionSpecialist.ouro/psyche/identities/nagini.md +31 -0
  10. package/AdoptionSpecialist.ouro/psyche/identities/ouroboros.md +31 -0
  11. package/AdoptionSpecialist.ouro/psyche/identities/python.md +31 -0
  12. package/AdoptionSpecialist.ouro/psyche/identities/quetzalcoatl.md +31 -0
  13. package/AdoptionSpecialist.ouro/psyche/identities/sir-hiss.md +31 -0
  14. package/AdoptionSpecialist.ouro/psyche/identities/the-serpent.md +31 -0
  15. package/AdoptionSpecialist.ouro/psyche/identities/the-snake.md +31 -0
  16. package/README.md +224 -6
  17. package/dist/heart/agent-entry.js +17 -0
  18. package/dist/heart/api-error.js +34 -0
  19. package/dist/heart/config.js +296 -0
  20. package/dist/heart/core.js +515 -0
  21. package/dist/heart/daemon/daemon-cli.js +675 -0
  22. package/dist/heart/daemon/daemon-entry.js +74 -0
  23. package/dist/heart/daemon/daemon.js +313 -0
  24. package/dist/heart/daemon/hatch-flow.js +285 -0
  25. package/dist/heart/daemon/hatch-specialist.js +107 -0
  26. package/dist/heart/daemon/health-monitor.js +79 -0
  27. package/dist/heart/daemon/log-tailer.js +146 -0
  28. package/dist/heart/daemon/message-router.js +98 -0
  29. package/dist/heart/daemon/os-cron.js +260 -0
  30. package/dist/heart/daemon/ouro-bot-entry.js +23 -0
  31. package/dist/heart/daemon/ouro-bot-wrapper.js +90 -0
  32. package/dist/heart/daemon/ouro-entry.js +23 -0
  33. package/dist/heart/daemon/ouro-uti.js +212 -0
  34. package/dist/heart/daemon/process-manager.js +237 -0
  35. package/dist/heart/daemon/runtime-logging.js +98 -0
  36. package/dist/heart/daemon/subagent-installer.js +125 -0
  37. package/dist/heart/daemon/task-scheduler.js +240 -0
  38. package/dist/heart/harness.js +26 -0
  39. package/dist/heart/identity.js +281 -0
  40. package/dist/heart/kicks.js +144 -0
  41. package/dist/heart/primitives.js +4 -0
  42. package/dist/heart/providers/anthropic.js +329 -0
  43. package/dist/heart/providers/azure.js +66 -0
  44. package/dist/heart/providers/minimax.js +53 -0
  45. package/dist/heart/providers/openai-codex.js +162 -0
  46. package/dist/heart/streaming.js +412 -0
  47. package/dist/heart/turn-coordinator.js +62 -0
  48. package/dist/inner-worker-entry.js +4 -0
  49. package/dist/mind/associative-recall.js +197 -0
  50. package/dist/mind/bundle-manifest.js +118 -0
  51. package/dist/mind/context.js +302 -0
  52. package/dist/mind/first-impressions.js +43 -0
  53. package/dist/mind/format.js +56 -0
  54. package/dist/mind/friends/channel.js +41 -0
  55. package/dist/mind/friends/resolver.js +84 -0
  56. package/dist/mind/friends/store-file.js +171 -0
  57. package/dist/mind/friends/store.js +4 -0
  58. package/dist/mind/friends/tokens.js +26 -0
  59. package/dist/mind/friends/types.js +21 -0
  60. package/dist/mind/memory.js +388 -0
  61. package/dist/mind/pending.js +93 -0
  62. package/dist/mind/phrases.js +43 -0
  63. package/dist/mind/prompt-refresh.js +20 -0
  64. package/dist/mind/prompt.js +352 -0
  65. package/dist/mind/token-estimate.js +119 -0
  66. package/dist/nerves/cli-logging.js +31 -0
  67. package/dist/nerves/coverage/audit-rules.js +81 -0
  68. package/dist/nerves/coverage/audit.js +200 -0
  69. package/dist/nerves/coverage/cli-main.js +5 -0
  70. package/dist/nerves/coverage/cli.js +51 -0
  71. package/dist/nerves/coverage/contract.js +23 -0
  72. package/dist/nerves/coverage/file-completeness.js +56 -0
  73. package/dist/nerves/coverage/run-artifacts.js +77 -0
  74. package/dist/nerves/coverage/source-scanner.js +34 -0
  75. package/dist/nerves/index.js +152 -0
  76. package/dist/nerves/runtime.js +38 -0
  77. package/dist/repertoire/ado-client.js +211 -0
  78. package/dist/repertoire/ado-context.js +73 -0
  79. package/dist/repertoire/ado-semantic.js +841 -0
  80. package/dist/repertoire/ado-templates.js +146 -0
  81. package/dist/repertoire/coding/index.js +36 -0
  82. package/dist/repertoire/coding/manager.js +489 -0
  83. package/dist/repertoire/coding/monitor.js +60 -0
  84. package/dist/repertoire/coding/reporter.js +45 -0
  85. package/dist/repertoire/coding/spawner.js +102 -0
  86. package/dist/repertoire/coding/tools.js +167 -0
  87. package/dist/repertoire/coding/types.js +2 -0
  88. package/dist/repertoire/data/ado-endpoints.json +122 -0
  89. package/dist/repertoire/data/graph-endpoints.json +212 -0
  90. package/dist/repertoire/github-client.js +64 -0
  91. package/dist/repertoire/graph-client.js +118 -0
  92. package/dist/repertoire/skills.js +156 -0
  93. package/dist/repertoire/tasks/board.js +122 -0
  94. package/dist/repertoire/tasks/index.js +210 -0
  95. package/dist/repertoire/tasks/lifecycle.js +80 -0
  96. package/dist/repertoire/tasks/middleware.js +65 -0
  97. package/dist/repertoire/tasks/parser.js +173 -0
  98. package/dist/repertoire/tasks/scanner.js +132 -0
  99. package/dist/repertoire/tasks/transitions.js +145 -0
  100. package/dist/repertoire/tasks/types.js +2 -0
  101. package/dist/repertoire/tools-base.js +714 -0
  102. package/dist/repertoire/tools-github.js +53 -0
  103. package/dist/repertoire/tools-teams.js +308 -0
  104. package/dist/repertoire/tools.js +199 -0
  105. package/dist/senses/cli-entry.js +15 -0
  106. package/dist/senses/cli.js +604 -0
  107. package/dist/senses/commands.js +98 -0
  108. package/dist/senses/inner-dialog-worker.js +61 -0
  109. package/dist/senses/inner-dialog.js +231 -0
  110. package/dist/senses/session-lock.js +119 -0
  111. package/dist/senses/teams-entry.js +15 -0
  112. package/dist/senses/teams.js +696 -0
  113. package/dist/senses/trust-gate.js +150 -0
  114. package/package.json +34 -11
  115. package/subagents/README.md +73 -0
  116. package/subagents/work-doer.md +233 -0
  117. package/subagents/work-merger.md +624 -0
  118. package/subagents/work-planner.md +373 -0
  119. package/bin/ouro.js +0 -6
@@ -0,0 +1,200 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readEvents = readEvents;
4
+ exports.collectObservedEventKeys = collectObservedEventKeys;
5
+ exports.validateSchemaAndRedaction = validateSchemaAndRedaction;
6
+ exports.auditNervesCoverage = auditNervesCoverage;
7
+ const fs_1 = require("fs");
8
+ const path_1 = require("path");
9
+ const contract_1 = require("./contract");
10
+ const audit_rules_1 = require("./audit-rules");
11
+ const source_scanner_1 = require("./source-scanner");
12
+ const file_completeness_1 = require("./file-completeness");
13
+ function readEvents(eventsPath) {
14
+ if (!(0, fs_1.existsSync)(eventsPath))
15
+ return [];
16
+ const raw = (0, fs_1.readFileSync)(eventsPath, "utf8");
17
+ const lines = raw.split("\n").map((line) => line.trim()).filter(Boolean);
18
+ const parsed = [];
19
+ for (const line of lines) {
20
+ try {
21
+ parsed.push(JSON.parse(line));
22
+ }
23
+ catch {
24
+ // Keep parsing resilient for audit mode; malformed lines are handled
25
+ // by schema violations below.
26
+ parsed.push({});
27
+ }
28
+ }
29
+ return parsed;
30
+ }
31
+ function collectObservedEventKeys(events) {
32
+ const observed = new Set();
33
+ for (const entry of events) {
34
+ if (typeof entry.component === "string" && typeof entry.event === "string") {
35
+ observed.add(`${entry.component}:${entry.event}`);
36
+ }
37
+ }
38
+ return [...observed].sort();
39
+ }
40
+ function validateSchemaAndRedaction(events) {
41
+ const violations = [];
42
+ events.forEach((entry, idx) => {
43
+ for (const key of contract_1.REQUIRED_ENVELOPE_FIELDS) {
44
+ if (!(key in entry)) {
45
+ violations.push(`event[${idx}] missing field '${key}'`);
46
+ }
47
+ }
48
+ const mustBeString = ["ts", "level", "event", "trace_id", "component", "message"];
49
+ for (const key of mustBeString) {
50
+ const value = entry[key];
51
+ if (typeof value !== "string" || value.trim().length === 0) {
52
+ violations.push(`event[${idx}] invalid '${key}'`);
53
+ }
54
+ }
55
+ if (typeof entry.meta !== "object" || entry.meta === null || Array.isArray(entry.meta)) {
56
+ violations.push(`event[${idx}] invalid 'meta'`);
57
+ }
58
+ const message = typeof entry.message === "string" ? entry.message : "";
59
+ const metaText = JSON.stringify(entry.meta ?? {});
60
+ for (const pattern of contract_1.SENSITIVE_PATTERNS) {
61
+ if (pattern.test(message) || pattern.test(metaText)) {
62
+ violations.push(`event[${idx}] matched redaction policy '${pattern.source}'`);
63
+ }
64
+ }
65
+ });
66
+ return violations;
67
+ }
68
+ function readPerTestData(perTestPath) {
69
+ if (!perTestPath || !(0, fs_1.existsSync)(perTestPath))
70
+ return null;
71
+ try {
72
+ return JSON.parse((0, fs_1.readFileSync)(perTestPath, "utf8"));
73
+ }
74
+ catch {
75
+ return null;
76
+ }
77
+ }
78
+ function scanSourceFiles(sourceRoot) {
79
+ const filesWithKeys = new Map();
80
+ const fileContents = new Map();
81
+ if (!sourceRoot || !(0, fs_1.existsSync)(sourceRoot)) {
82
+ return { filesWithKeys, fileContents };
83
+ }
84
+ const root = sourceRoot;
85
+ function walkDir(dir) {
86
+ for (const entry of (0, fs_1.readdirSync)(dir, { withFileTypes: true })) {
87
+ const full = (0, path_1.join)(dir, entry.name);
88
+ if (entry.isDirectory()) {
89
+ // Skip __tests__, nerves/, and reflection/ directories
90
+ // reflection/ tests mock emitNervesEvent, so events are not observed
91
+ if (entry.name === "__tests__" || entry.name === "nerves" || entry.name === "reflection")
92
+ continue;
93
+ walkDir(full);
94
+ }
95
+ else if (entry.name.endsWith(".ts")) {
96
+ const content = (0, fs_1.readFileSync)(full, "utf8");
97
+ const relPath = full.slice(root.length - "src".length);
98
+ fileContents.set(relPath, content);
99
+ const keys = (0, source_scanner_1.scanSourceForNervesKeys)(content);
100
+ if (keys.length > 0) {
101
+ filesWithKeys.set(relPath, keys);
102
+ }
103
+ }
104
+ }
105
+ }
106
+ walkDir(root);
107
+ return { filesWithKeys, fileContents };
108
+ }
109
+ function runSourceCoverage(filesWithKeys, observedKeys) {
110
+ const allDeclaredKeys = new Set();
111
+ for (const keys of filesWithKeys.values()) {
112
+ for (const key of keys) {
113
+ allDeclaredKeys.add(key);
114
+ }
115
+ }
116
+ const observedSet = new Set(observedKeys);
117
+ const missing = [...allDeclaredKeys].filter((key) => !observedSet.has(key)).sort();
118
+ return {
119
+ status: missing.length === 0 ? "pass" : "fail",
120
+ declared_keys: allDeclaredKeys.size,
121
+ observed_keys: observedKeys.length,
122
+ missing,
123
+ };
124
+ }
125
+ function auditNervesCoverage(input) {
126
+ const events = readEvents(input.eventsPath);
127
+ const observedKeys = collectObservedEventKeys(events);
128
+ // Schema & redaction check (preserved)
129
+ const schemaViolations = validateSchemaAndRedaction(events);
130
+ const schemaStatus = schemaViolations.length === 0 ? "pass" : "fail";
131
+ // Per-test data for Rules 1-3
132
+ const perTestData = readPerTestData(input.perTestPath);
133
+ const everyTestEmits = (0, audit_rules_1.checkEveryTestEmits)(perTestData);
134
+ const startEndPairing = (0, audit_rules_1.checkStartEndPairing)(perTestData);
135
+ const errorContext = (0, audit_rules_1.checkErrorContext)(perTestData);
136
+ // Source scanning for Rules 4-5
137
+ const { filesWithKeys, fileContents } = scanSourceFiles(input.sourceRoot);
138
+ const sourceCoverage = runSourceCoverage(filesWithKeys, observedKeys);
139
+ const fileCompleteness = (0, file_completeness_1.checkFileCompleteness)(filesWithKeys, fileContents);
140
+ // Aggregate
141
+ const requiredActions = [];
142
+ if (schemaStatus === "fail") {
143
+ requiredActions.push({
144
+ type: "logging",
145
+ target: "schema-redaction",
146
+ reason: `schema/redaction violations: ${schemaViolations.slice(0, 3).join("; ")}`,
147
+ });
148
+ }
149
+ if (everyTestEmits.status === "fail") {
150
+ requiredActions.push({
151
+ type: "logging",
152
+ target: "every-test-emits",
153
+ reason: `${everyTestEmits.silent_tests.length} test(s) emitted zero events`,
154
+ });
155
+ }
156
+ if (startEndPairing.status === "fail") {
157
+ requiredActions.push({
158
+ type: "logging",
159
+ target: "start-end-pairing",
160
+ reason: `${startEndPairing.unmatched.length} unmatched _start event(s)`,
161
+ });
162
+ }
163
+ if (errorContext.status === "fail") {
164
+ requiredActions.push({
165
+ type: "logging",
166
+ target: "error-context",
167
+ reason: `${errorContext.violations.length} error event(s) missing context`,
168
+ });
169
+ }
170
+ // Source-coverage is advisory (warn) -- many test files mock emitNervesEvent,
171
+ // so declared keys are not observed in the global capture sink. This rule
172
+ // becomes enforceable once tests use spyOn instead of full mocks.
173
+ // if (sourceCoverage.status === "fail") {
174
+ // requiredActions.push({ ... })
175
+ // }
176
+ if (fileCompleteness.status === "fail") {
177
+ requiredActions.push({
178
+ type: "logging",
179
+ target: "file-completeness",
180
+ reason: `${fileCompleteness.missing.length} file(s) missing emitNervesEvent: ${fileCompleteness.missing.slice(0, 5).join(", ")}`,
181
+ });
182
+ }
183
+ const overallStatus = requiredActions.length === 0 ? "pass" : "fail";
184
+ return {
185
+ overall_status: overallStatus,
186
+ required_actions: requiredActions,
187
+ nerves_coverage: {
188
+ schema_redaction: {
189
+ status: schemaStatus,
190
+ checked_events: events.length,
191
+ violations: schemaViolations,
192
+ },
193
+ every_test_emits: everyTestEmits,
194
+ start_end_pairing: startEndPairing,
195
+ error_context: errorContext,
196
+ source_coverage: sourceCoverage,
197
+ file_completeness: fileCompleteness,
198
+ },
199
+ };
200
+ }
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const cli_1 = require("./cli");
4
+ const code = (0, cli_1.runAuditCli)(process.argv.slice(2));
5
+ process.exit(code);
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runAuditCli = runAuditCli;
4
+ const fs_1 = require("fs");
5
+ const path_1 = require("path");
6
+ const audit_1 = require("./audit");
7
+ const run_artifacts_1 = require("./run-artifacts");
8
+ function parseArgs(argv) {
9
+ const args = {};
10
+ for (let i = 0; i < argv.length; i++) {
11
+ const token = argv[i];
12
+ const next = argv[i + 1];
13
+ if (!next)
14
+ continue;
15
+ if (token === "--run-dir")
16
+ args.runDir = next;
17
+ if (token === "--events-path")
18
+ args.eventsPath = next;
19
+ if (token === "--per-test-path")
20
+ args.perTestPath = next;
21
+ if (token === "--source-root")
22
+ args.sourceRoot = next;
23
+ if (token === "--output")
24
+ args.output = next;
25
+ }
26
+ return args;
27
+ }
28
+ function runAuditCli(argv) {
29
+ const args = parseArgs(argv);
30
+ const latestRun = (0, run_artifacts_1.readLatestRun)();
31
+ const runDir = args.runDir ?? latestRun?.run_dir;
32
+ if (!runDir) {
33
+ // eslint-disable-next-line no-console -- meta-tooling: audit error message
34
+ console.error("nerves audit: no run directory found; provide --run-dir");
35
+ return 2;
36
+ }
37
+ const eventsPath = args.eventsPath ?? (0, path_1.join)(runDir, "vitest-events.ndjson");
38
+ const perTestPath = args.perTestPath ?? (0, path_1.join)(runDir, "vitest-events-per-test.json");
39
+ const sourceRoot = args.sourceRoot ?? (0, path_1.resolve)("src");
40
+ const outputPath = args.output ?? (0, path_1.join)(runDir, "nerves-coverage.json");
41
+ const report = (0, audit_1.auditNervesCoverage)({
42
+ eventsPath,
43
+ perTestPath,
44
+ sourceRoot,
45
+ });
46
+ (0, fs_1.mkdirSync)((0, path_1.dirname)(outputPath), { recursive: true });
47
+ (0, fs_1.writeFileSync)(outputPath, JSON.stringify(report, null, 2), "utf8");
48
+ // eslint-disable-next-line no-console -- meta-tooling: audit result message
49
+ console.log(`nerves audit: ${report.overall_status} (${outputPath})`);
50
+ return report.overall_status === "pass" ? 0 : 1;
51
+ }
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SENSITIVE_PATTERNS = exports.REQUIRED_ENVELOPE_FIELDS = void 0;
4
+ exports.eventKey = eventKey;
5
+ exports.REQUIRED_ENVELOPE_FIELDS = [
6
+ "ts",
7
+ "level",
8
+ "event",
9
+ "trace_id",
10
+ "component",
11
+ "message",
12
+ "meta",
13
+ ];
14
+ exports.SENSITIVE_PATTERNS = [
15
+ /\btoken\s*[:=]/i,
16
+ /\bapi[_-]?key\b/i,
17
+ /\bpassword\b/i,
18
+ /\bsecret\b/i,
19
+ /\bauthorization\b/i,
20
+ ];
21
+ function eventKey(component, event) {
22
+ return `${component}:${event}`;
23
+ }
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ /**
3
+ * File completeness check (Rule 5).
4
+ *
5
+ * Every production file with executable code must have at least one
6
+ * emitNervesEvent call. Type-only files (containing only type/interface/enum
7
+ * declarations) are exempt.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.isTypeOnlyFile = isTypeOnlyFile;
11
+ exports.checkFileCompleteness = checkFileCompleteness;
12
+ /**
13
+ * Determines if a source file is type-only (no executable code).
14
+ * A file is type-only if it contains no function, class, or mutable declarations.
15
+ * `const ... as const` declarations are treated as type-equivalent (frozen
16
+ * compile-time values with no side effects).
17
+ */
18
+ function isTypeOnlyFile(source) {
19
+ const lines = source.split("\n");
20
+ for (const line of lines) {
21
+ const trimmed = line.trim();
22
+ // Skip lines that are const+as-const (type-equivalent frozen values)
23
+ if (/\bconst\s/.test(trimmed) && /\bas\s+const\b/.test(trimmed))
24
+ continue;
25
+ // Check for executable code markers
26
+ if (/\b(function|class|const|let|var)\s/.test(trimmed))
27
+ return false;
28
+ }
29
+ return true;
30
+ }
31
+ /**
32
+ * Check that all production files have at least one emitNervesEvent call.
33
+ *
34
+ * @param filesWithKeys - Map of filePath -> keys found by source scanner
35
+ * @param fileContents - Map of filePath -> source content for ALL production files
36
+ */
37
+ function checkFileCompleteness(filesWithKeys, fileContents) {
38
+ const missing = [];
39
+ const exempt = [];
40
+ for (const [filePath, source] of fileContents) {
41
+ const hasKeys = filesWithKeys.has(filePath) && filesWithKeys.get(filePath).length > 0;
42
+ if (hasKeys)
43
+ continue;
44
+ if (isTypeOnlyFile(source)) {
45
+ exempt.push(filePath);
46
+ }
47
+ else {
48
+ missing.push(filePath);
49
+ }
50
+ }
51
+ return {
52
+ status: missing.length === 0 ? "pass" : "fail",
53
+ missing: missing.sort(),
54
+ exempt: exempt.sort(),
55
+ };
56
+ }
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.REPO_SLUG = void 0;
4
+ exports.getTestRunsRoot = getTestRunsRoot;
5
+ exports.createRunId = createRunId;
6
+ exports.getRunDir = getRunDir;
7
+ exports.writeActiveRun = writeActiveRun;
8
+ exports.readActiveRun = readActiveRun;
9
+ exports.clearActiveRun = clearActiveRun;
10
+ exports.writeLatestRun = writeLatestRun;
11
+ exports.readLatestRun = readLatestRun;
12
+ const fs_1 = require("fs");
13
+ const path_1 = require("path");
14
+ const os_1 = require("os");
15
+ exports.REPO_SLUG = "ouroboros-agent-harness";
16
+ function getTestRunsRoot(repoSlug = exports.REPO_SLUG) {
17
+ return (0, path_1.join)((0, os_1.homedir)(), ".agentstate", "test-runs", repoSlug);
18
+ }
19
+ function createRunId(now = new Date()) {
20
+ return now.toISOString().replace(/[:.]/g, "-");
21
+ }
22
+ function getRunDir(runId, repoSlug = exports.REPO_SLUG) {
23
+ return (0, path_1.join)(getTestRunsRoot(repoSlug), runId);
24
+ }
25
+ function getActiveRunPath(repoSlug = exports.REPO_SLUG) {
26
+ return (0, path_1.join)(getTestRunsRoot(repoSlug), ".active-run.json");
27
+ }
28
+ function getLatestRunPath(repoSlug = exports.REPO_SLUG) {
29
+ return (0, path_1.join)(getTestRunsRoot(repoSlug), "latest-run.json");
30
+ }
31
+ function ensureRoot(repoSlug = exports.REPO_SLUG) {
32
+ const root = getTestRunsRoot(repoSlug);
33
+ (0, fs_1.mkdirSync)(root, { recursive: true });
34
+ return root;
35
+ }
36
+ function writeActiveRun(info) {
37
+ ensureRoot(info.repo_slug);
38
+ (0, fs_1.writeFileSync)(getActiveRunPath(info.repo_slug), JSON.stringify(info, null, 2), "utf8");
39
+ }
40
+ function readActiveRun(repoSlug = exports.REPO_SLUG) {
41
+ const filePath = getActiveRunPath(repoSlug);
42
+ if (!(0, fs_1.existsSync)(filePath))
43
+ return null;
44
+ try {
45
+ const parsed = JSON.parse((0, fs_1.readFileSync)(filePath, "utf8"));
46
+ if (!parsed.run_id || !parsed.run_dir)
47
+ return null;
48
+ return parsed;
49
+ }
50
+ catch {
51
+ return null;
52
+ }
53
+ }
54
+ function clearActiveRun(repoSlug = exports.REPO_SLUG) {
55
+ const filePath = getActiveRunPath(repoSlug);
56
+ if ((0, fs_1.existsSync)(filePath)) {
57
+ (0, fs_1.unlinkSync)(filePath);
58
+ }
59
+ }
60
+ function writeLatestRun(info) {
61
+ ensureRoot(info.repo_slug);
62
+ (0, fs_1.writeFileSync)(getLatestRunPath(info.repo_slug), JSON.stringify(info, null, 2), "utf8");
63
+ }
64
+ function readLatestRun(repoSlug = exports.REPO_SLUG) {
65
+ const filePath = getLatestRunPath(repoSlug);
66
+ if (!(0, fs_1.existsSync)(filePath))
67
+ return null;
68
+ try {
69
+ const parsed = JSON.parse((0, fs_1.readFileSync)(filePath, "utf8"));
70
+ if (!parsed.run_id || !parsed.run_dir)
71
+ return null;
72
+ return parsed;
73
+ }
74
+ catch {
75
+ return null;
76
+ }
77
+ }
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ /**
3
+ * Static source scanner for emitNervesEvent calls.
4
+ *
5
+ * Extracts component:event keys from production source files by
6
+ * regex-matching emitNervesEvent({ component: "...", event: "..." })
7
+ * calls. Only accepts static string literals (single or double quotes).
8
+ * Template literals and variable references are rejected.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.scanSourceForNervesKeys = scanSourceForNervesKeys;
12
+ const EMIT_CALL_RE = /emitNervesEvent\s*\(\s*\{([\s\S]*?)\}\s*\)/g;
13
+ function extractStringLiteral(block, field) {
14
+ const re = new RegExp(`${field}\\s*:\\s*(['"])((?:(?!\\1).)+)\\1`);
15
+ const match = re.exec(block);
16
+ return match ? match[2] : null;
17
+ }
18
+ /**
19
+ * Scan a source file's content for emitNervesEvent calls and extract
20
+ * component:event keys. Only static string literals are accepted.
21
+ */
22
+ function scanSourceForNervesKeys(source) {
23
+ const keys = new Set();
24
+ let match;
25
+ while ((match = EMIT_CALL_RE.exec(source)) !== null) {
26
+ const block = match[1];
27
+ const component = extractStringLiteral(block, "component");
28
+ const event = extractStringLiteral(block, "event");
29
+ if (component && event) {
30
+ keys.add(`${component}:${event}`);
31
+ }
32
+ }
33
+ return [...keys].sort();
34
+ }
@@ -0,0 +1,152 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createTraceId = createTraceId;
4
+ exports.ensureTraceId = ensureTraceId;
5
+ exports.createFanoutSink = createFanoutSink;
6
+ exports.formatTerminalEntry = formatTerminalEntry;
7
+ exports.createTerminalSink = createTerminalSink;
8
+ exports.createStderrSink = createStderrSink;
9
+ exports.createNdjsonFileSink = createNdjsonFileSink;
10
+ exports.registerGlobalLogSink = registerGlobalLogSink;
11
+ exports.createLogger = createLogger;
12
+ const fs_1 = require("fs");
13
+ const path_1 = require("path");
14
+ const crypto_1 = require("crypto");
15
+ const LEVEL_PRIORITY = {
16
+ debug: 10,
17
+ info: 20,
18
+ warn: 30,
19
+ error: 40,
20
+ };
21
+ const GLOBAL_SINKS_KEY = Symbol.for("ouroboros.nerves.global-sinks");
22
+ function resolveGlobalSinks() {
23
+ const scope = globalThis;
24
+ const existing = scope[GLOBAL_SINKS_KEY];
25
+ if (existing instanceof Set) {
26
+ return existing;
27
+ }
28
+ const created = new Set();
29
+ scope[GLOBAL_SINKS_KEY] = created;
30
+ return created;
31
+ }
32
+ const globalSinks = resolveGlobalSinks();
33
+ function shouldEmit(configuredLevel, eventLevel) {
34
+ return LEVEL_PRIORITY[eventLevel] >= LEVEL_PRIORITY[configuredLevel];
35
+ }
36
+ function createTraceId() {
37
+ return (0, crypto_1.randomUUID)();
38
+ }
39
+ function ensureTraceId(traceId) {
40
+ return traceId && traceId.trim() ? traceId : createTraceId();
41
+ }
42
+ function createFanoutSink(sinks) {
43
+ return (entry) => {
44
+ for (const sink of sinks) {
45
+ try {
46
+ sink(entry);
47
+ }
48
+ catch {
49
+ // Fanout must stay resilient: one sink failure cannot block others.
50
+ }
51
+ }
52
+ };
53
+ }
54
+ function formatTerminalTime(ts) {
55
+ const parsed = new Date(ts);
56
+ if (Number.isNaN(parsed.getTime())) {
57
+ return ts;
58
+ }
59
+ return parsed.toISOString().slice(11, 19);
60
+ }
61
+ function formatTerminalMeta(meta) {
62
+ if (Object.keys(meta).length === 0)
63
+ return "";
64
+ return ` ${JSON.stringify(meta)}`;
65
+ }
66
+ const LEVEL_COLORS = {
67
+ debug: "\x1b[2m",
68
+ info: "\x1b[36m",
69
+ warn: "\x1b[33m",
70
+ error: "\x1b[31m",
71
+ };
72
+ function formatTerminalEntry(entry) {
73
+ const level = entry.level.toUpperCase();
74
+ return `${formatTerminalTime(entry.ts)} ${level} [${entry.component}] ${entry.message}${formatTerminalMeta(entry.meta)}`;
75
+ }
76
+ function createTerminalSink(write = (chunk) => process.stderr.write(chunk), colorize = true) {
77
+ return (entry) => {
78
+ const line = formatTerminalEntry(entry);
79
+ if (!colorize) {
80
+ write(`${line}\n`);
81
+ return;
82
+ }
83
+ const prefix = LEVEL_COLORS[entry.level];
84
+ write(`${prefix}${line}\x1b[0m\n`);
85
+ };
86
+ }
87
+ function createStderrSink(write = (chunk) => process.stderr.write(chunk)) {
88
+ return createTerminalSink(write);
89
+ }
90
+ function createNdjsonFileSink(filePath) {
91
+ (0, fs_1.mkdirSync)((0, path_1.dirname)(filePath), { recursive: true });
92
+ const queue = [];
93
+ let flushing = false;
94
+ function flush() {
95
+ if (flushing || queue.length === 0)
96
+ return;
97
+ flushing = true;
98
+ const line = queue.shift();
99
+ (0, fs_1.appendFile)(filePath, line, "utf8", () => {
100
+ flushing = false;
101
+ flush();
102
+ });
103
+ }
104
+ return (entry) => {
105
+ queue.push(`${JSON.stringify(entry)}\n`);
106
+ flush();
107
+ };
108
+ }
109
+ function registerGlobalLogSink(sink) {
110
+ globalSinks.add(sink);
111
+ return () => {
112
+ globalSinks.delete(sink);
113
+ };
114
+ }
115
+ function emitToGlobalSinks(entry) {
116
+ for (const sink of globalSinks) {
117
+ try {
118
+ sink(entry);
119
+ }
120
+ catch {
121
+ // Never fail runtime logging if an auxiliary sink errors.
122
+ }
123
+ }
124
+ }
125
+ function createLogger(options = {}) {
126
+ const configuredLevel = options.level ?? "info";
127
+ const sinks = options.sinks ?? [createStderrSink()];
128
+ const sink = createFanoutSink(sinks);
129
+ const now = options.now ?? (() => new Date());
130
+ function emit(level, entry) {
131
+ if (!shouldEmit(configuredLevel, level)) {
132
+ return;
133
+ }
134
+ const payload = {
135
+ ts: now().toISOString(),
136
+ level,
137
+ event: entry.event,
138
+ trace_id: entry.trace_id,
139
+ component: entry.component,
140
+ message: entry.message,
141
+ meta: entry.meta,
142
+ };
143
+ sink(payload);
144
+ emitToGlobalSinks(payload);
145
+ }
146
+ return {
147
+ debug: (entry) => emit("debug", entry),
148
+ info: (entry) => emit("info", entry),
149
+ warn: (entry) => emit("warn", entry),
150
+ error: (entry) => emit("error", entry),
151
+ };
152
+ }
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setRuntimeLogger = setRuntimeLogger;
4
+ exports.emitNervesEvent = emitNervesEvent;
5
+ const index_1 = require("./index");
6
+ let runtimeLogger = null;
7
+ function getRuntimeLogger() {
8
+ if (!runtimeLogger) {
9
+ runtimeLogger = (0, index_1.createLogger)({ level: "info" });
10
+ }
11
+ return runtimeLogger;
12
+ }
13
+ function setRuntimeLogger(logger) {
14
+ runtimeLogger = logger;
15
+ }
16
+ function emitNervesEvent(event) {
17
+ const logger = getRuntimeLogger();
18
+ const payload = {
19
+ event: event.event,
20
+ trace_id: (0, index_1.ensureTraceId)(event.trace_id),
21
+ component: event.component,
22
+ message: event.message,
23
+ meta: event.meta ?? {},
24
+ };
25
+ const level = event.level ?? "info";
26
+ if (level === "debug") {
27
+ logger.debug(payload);
28
+ }
29
+ else if (level === "warn") {
30
+ logger.warn(payload);
31
+ }
32
+ else if (level === "error") {
33
+ logger.error(payload);
34
+ }
35
+ else {
36
+ logger.info(payload);
37
+ }
38
+ }