@sireai/optimus 0.1.34 → 0.1.37

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 (45) hide show
  1. package/dist/cli/optimus.js +41 -22
  2. package/dist/cli/optimus.js.map +1 -1
  3. package/dist/integrations/jira/jira-access-manager.d.ts +1 -0
  4. package/dist/integrations/jira/jira-access-manager.js +24 -0
  5. package/dist/integrations/jira/jira-access-manager.js.map +1 -1
  6. package/dist/problem-solving-core/codex/codex-runner.d.ts +2 -0
  7. package/dist/problem-solving-core/codex/codex-runner.js +43 -28
  8. package/dist/problem-solving-core/codex/codex-runner.js.map +1 -1
  9. package/dist/task-environment/delivery/task-delivery-service.js +3 -1
  10. package/dist/task-environment/delivery/task-delivery-service.js.map +1 -1
  11. package/dist/task-environment/evidence/evidence-preparation-service.js +2 -1
  12. package/dist/task-environment/evidence/evidence-preparation-service.js.map +1 -1
  13. package/dist/task-environment/intake/manual-problem-intake.js +30 -0
  14. package/dist/task-environment/intake/manual-problem-intake.js.map +1 -1
  15. package/dist/task-environment/orchestration/execution-context-assembler.d.ts +1 -0
  16. package/dist/task-environment/orchestration/execution-context-assembler.js +50 -0
  17. package/dist/task-environment/orchestration/execution-context-assembler.js.map +1 -1
  18. package/dist/task-environment/orchestration/task-orchestrator.d.ts +1 -0
  19. package/dist/task-environment/orchestration/task-orchestrator.js +27 -5
  20. package/dist/task-environment/orchestration/task-orchestrator.js.map +1 -1
  21. package/dist/task-environment/orchestration/task-package-inputs.d.ts +2 -0
  22. package/dist/task-environment/orchestration/task-package-inputs.js +38 -0
  23. package/dist/task-environment/orchestration/task-package-inputs.js.map +1 -0
  24. package/dist/task-environment/orchestration/task-runtime-policy.d.ts +4 -0
  25. package/dist/task-environment/orchestration/task-runtime-policy.js +38 -0
  26. package/dist/task-environment/orchestration/task-runtime-policy.js.map +1 -0
  27. package/dist/types.d.ts +32 -0
  28. package/embedded-skills/task/bugfix/android-hprof-analyzer/SKILL.md +37 -0
  29. package/embedded-skills/task/bugfix/android-hprof-analyzer/runtime/README.md +11 -0
  30. package/embedded-skills/task/bugfix/android-hprof-analyzer/scripts/analyze-hprof.mjs +286 -0
  31. package/embedded-skills/task/bugfix/android-hprof-analyzer/scripts/ensure-shark-runtime.mjs +213 -0
  32. package/embedded-skills/task/bugfix/android-hprof-analyzer/scripts/run-shark.sh +27 -0
  33. package/embedded-skills/task/bugfix/android-hprof-analyzer/skill.json +6 -0
  34. package/package.json +1 -1
  35. package/task-harnesses/bugfix/CONSTRAINTS.md +2 -0
  36. package/task-harnesses/bugfix/ROLE.md +4 -0
  37. package/task-harnesses/bugfix/STANDARD.md +1 -0
  38. package/task-harnesses/pm/ACCEPT.md +94 -0
  39. package/task-harnesses/pm/CONSTRAINTS.md +27 -0
  40. package/task-harnesses/pm/CONTEXT.md +26 -0
  41. package/task-harnesses/pm/EVOLUTION.md +35 -0
  42. package/task-harnesses/pm/ROLE.md +59 -0
  43. package/task-harnesses/pm/STANDARD.md +125 -0
  44. package/task-harnesses/pm/manifest.json +13 -0
  45. package/task-harnesses/registry.json +4 -0
@@ -0,0 +1,286 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { execFile } from "node:child_process";
4
+ import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
5
+ import { basename, dirname, extname, join, relative, resolve } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import { promisify } from "node:util";
8
+
9
+ const execFileAsync = promisify(execFile);
10
+ const SCRIPT_DIR = resolve(dirname(fileURLToPath(import.meta.url)));
11
+ const RUNNER_PATH = join(SCRIPT_DIR, "run-shark.sh");
12
+
13
+ async function main() {
14
+ const options = parseArgs(process.argv.slice(2));
15
+ if (!options.outputDir) {
16
+ throw new Error("analyze-hprof requires --output <dir>.");
17
+ }
18
+ if (!options.manifestPath && !options.evidenceDir) {
19
+ throw new Error("analyze-hprof requires --manifest <path> or --evidence-dir <dir>.");
20
+ }
21
+
22
+ const outputDir = resolve(options.outputDir);
23
+ const rawDir = join(outputDir, "raw");
24
+ await mkdir(rawDir, { recursive: true });
25
+
26
+ const evidenceFiles = await discoverHprofFiles(options);
27
+ if (evidenceFiles.length === 0) {
28
+ const emptySummary = {
29
+ tool: "embedded-shark",
30
+ status: "failed",
31
+ files: [],
32
+ findings: [],
33
+ warnings: ["No evidence files whose basename contains 'hprof' were found."]
34
+ };
35
+ await writeOutputs(outputDir, emptySummary, ["No HPROF evidence files were found."]);
36
+ process.exitCode = 1;
37
+ return;
38
+ }
39
+
40
+ const files = [];
41
+ const warnings = [];
42
+ for (const [index, filePath] of evidenceFiles.entries()) {
43
+ const rawPath = join(rawDir, `${String(index + 1).padStart(2, "0")}-${sanitizeName(basename(filePath))}.txt`);
44
+ const args = [];
45
+ if (options.mappingPath) {
46
+ args.push("--obfuscation-mapping", resolve(options.mappingPath));
47
+ }
48
+ args.push("--hprof", filePath, "analyze");
49
+
50
+ try {
51
+ const { stdout, stderr } = await execFileAsync(RUNNER_PATH, args, {
52
+ cwd: process.cwd(),
53
+ maxBuffer: 20 * 1024 * 1024,
54
+ env: process.env
55
+ });
56
+ const combined = [stdout, stderr].filter(Boolean).join("\n").trim();
57
+ await writeFile(rawPath, combined || "(no output)\n", "utf8");
58
+ files.push({
59
+ path: filePath,
60
+ analyzed: true,
61
+ rawOutputPath: rawPath,
62
+ findings: extractFindingHints(combined)
63
+ });
64
+ } catch (error) {
65
+ const stdout = error?.stdout ? String(error.stdout) : "";
66
+ const stderr = error?.stderr ? String(error.stderr) : error instanceof Error ? error.message : String(error);
67
+ const combined = [stdout, stderr].filter(Boolean).join("\n").trim();
68
+ await writeFile(rawPath, combined || "(no output)\n", "utf8");
69
+ const reason = classifyRunnerFailure(combined, error);
70
+ warnings.push(`${basename(filePath)}: ${reason}`);
71
+ files.push({
72
+ path: filePath,
73
+ analyzed: false,
74
+ rawOutputPath: rawPath,
75
+ error: reason,
76
+ findings: []
77
+ });
78
+ }
79
+ }
80
+
81
+ const findings = files.flatMap((entry) => entry.findings.map((finding) => ({
82
+ sourceFile: entry.path,
83
+ ...finding
84
+ })));
85
+ const analyzedCount = files.filter((entry) => entry.analyzed).length;
86
+ const status = analyzedCount === files.length
87
+ ? "ok"
88
+ : analyzedCount > 0
89
+ ? "partial"
90
+ : "failed";
91
+ const summary = {
92
+ tool: "embedded-shark",
93
+ status,
94
+ files,
95
+ findings,
96
+ warnings
97
+ };
98
+
99
+ const markdownLines = [
100
+ "# HPROF Analysis Summary",
101
+ "",
102
+ `- Status: ${status}`,
103
+ `- Files discovered: ${files.length}`,
104
+ `- Files analyzed: ${analyzedCount}`,
105
+ "",
106
+ "## Files"
107
+ ];
108
+ for (const entry of files) {
109
+ markdownLines.push(
110
+ `- ${relative(outputDir, entry.path) || entry.path}: ${entry.analyzed ? "analyzed" : `failed (${entry.error})`}`,
111
+ ` - Raw Output: ${relative(outputDir, entry.rawOutputPath) || entry.rawOutputPath}`
112
+ );
113
+ for (const finding of entry.findings) {
114
+ markdownLines.push(` - Finding: ${finding.target}${finding.retainPath ? ` | Path: ${finding.retainPath}` : ""}`);
115
+ }
116
+ }
117
+ if (warnings.length > 0) {
118
+ markdownLines.push("", "## Warnings");
119
+ for (const warning of warnings) {
120
+ markdownLines.push(`- ${warning}`);
121
+ }
122
+ }
123
+
124
+ await writeOutputs(outputDir, summary, markdownLines);
125
+ if (status === "failed") {
126
+ process.exitCode = 1;
127
+ }
128
+ }
129
+
130
+ function parseArgs(argv) {
131
+ const options = {};
132
+ for (let index = 0; index < argv.length; index += 1) {
133
+ const arg = argv[index];
134
+ const next = argv[index + 1];
135
+ if (!arg.startsWith("--")) {
136
+ continue;
137
+ }
138
+ if (!next || next.startsWith("--")) {
139
+ throw new Error(`Missing value for ${arg}.`);
140
+ }
141
+ if (arg === "--manifest") {
142
+ options.manifestPath = next;
143
+ index += 1;
144
+ continue;
145
+ }
146
+ if (arg === "--evidence-dir") {
147
+ options.evidenceDir = next;
148
+ index += 1;
149
+ continue;
150
+ }
151
+ if (arg === "--output") {
152
+ options.outputDir = next;
153
+ index += 1;
154
+ continue;
155
+ }
156
+ if (arg === "--mapping") {
157
+ options.mappingPath = next;
158
+ index += 1;
159
+ continue;
160
+ }
161
+ throw new Error(`Unknown argument: ${arg}`);
162
+ }
163
+ return options;
164
+ }
165
+
166
+ async function discoverHprofFiles(options) {
167
+ const discovered = new Set();
168
+ if (options.manifestPath) {
169
+ const manifestPath = resolve(options.manifestPath);
170
+ const manifestDir = dirname(manifestPath);
171
+ const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
172
+ for (const value of collectPathValues(manifest)) {
173
+ await maybeAddFile(resolve(manifestDir, value), discovered);
174
+ }
175
+ await walkForHprofFiles(manifestDir, discovered);
176
+ }
177
+ if (options.evidenceDir) {
178
+ await walkForHprofFiles(resolve(options.evidenceDir), discovered);
179
+ }
180
+ return [...discovered].sort((left, right) => left.localeCompare(right));
181
+ }
182
+
183
+ function collectPathValues(node, values = []) {
184
+ if (!node) {
185
+ return values;
186
+ }
187
+ if (Array.isArray(node)) {
188
+ for (const item of node) {
189
+ collectPathValues(item, values);
190
+ }
191
+ return values;
192
+ }
193
+ if (typeof node === "object") {
194
+ for (const [key, value] of Object.entries(node)) {
195
+ if (typeof value === "string" && key.toLowerCase().endsWith("path")) {
196
+ values.push(value);
197
+ } else {
198
+ collectPathValues(value, values);
199
+ }
200
+ }
201
+ }
202
+ return values;
203
+ }
204
+
205
+ async function walkForHprofFiles(rootDir, discovered) {
206
+ let entries = [];
207
+ try {
208
+ entries = await readdir(rootDir, { withFileTypes: true });
209
+ } catch {
210
+ return;
211
+ }
212
+ for (const entry of entries) {
213
+ const entryPath = join(rootDir, entry.name);
214
+ if (entry.isDirectory()) {
215
+ await walkForHprofFiles(entryPath, discovered);
216
+ } else if (entry.isFile()) {
217
+ await maybeAddFile(entryPath, discovered);
218
+ }
219
+ }
220
+ }
221
+
222
+ async function maybeAddFile(filePath, discovered) {
223
+ if (!basename(filePath).toLowerCase().includes("hprof")) {
224
+ return;
225
+ }
226
+ try {
227
+ const info = await stat(filePath);
228
+ if (info.isFile()) {
229
+ discovered.add(filePath);
230
+ }
231
+ } catch {
232
+ // Ignore missing paths recorded in manifests.
233
+ }
234
+ }
235
+
236
+ function extractFindingHints(output) {
237
+ if (!output) {
238
+ return [];
239
+ }
240
+ const hints = [];
241
+ const retainedPattern = /([A-Za-z0-9_$.]+)\s+leaking/giu;
242
+ const pathPattern = /GC Root(?:.|\n){0,300}/iu;
243
+ const targets = [...output.matchAll(retainedPattern)].map((match) => match[1]).slice(0, 5);
244
+ const pathMatch = output.match(pathPattern)?.[0]?.replace(/\s+/gu, " ").trim();
245
+ for (const target of targets) {
246
+ hints.push({
247
+ target,
248
+ ...(pathMatch ? { retainPath: pathMatch } : {})
249
+ });
250
+ }
251
+ return hints;
252
+ }
253
+
254
+ function classifyRunnerFailure(output, error) {
255
+ const text = `${output}\n${error instanceof Error ? error.message : ""}`.toLowerCase();
256
+ if (text.includes("java runtime is required") || text.includes("no 'java' command could be found")) {
257
+ return "missing_java";
258
+ }
259
+ if (text.includes("embedded shark runner is unavailable")) {
260
+ return "missing_embedded_runtime";
261
+ }
262
+ if (text.includes("file not found") || text.includes("no such file")) {
263
+ return "missing_hprof_file";
264
+ }
265
+ if (text.includes("heap dump") || text.includes("hprof")) {
266
+ return "runner_failed";
267
+ }
268
+ return "runner_failed";
269
+ }
270
+
271
+ async function writeOutputs(outputDir, summary, markdownLines) {
272
+ await mkdir(outputDir, { recursive: true });
273
+ await writeFile(join(outputDir, "summary.json"), JSON.stringify(summary, null, 2), "utf8");
274
+ await writeFile(join(outputDir, "summary.md"), `${markdownLines.join("\n")}\n`, "utf8");
275
+ }
276
+
277
+ function sanitizeName(name) {
278
+ const extension = extname(name);
279
+ const base = extension ? name.slice(0, -extension.length) : name;
280
+ return `${base.replace(/[^\w.-]+/gu, "_").slice(0, 120) || "hprof"}${extension || ".txt"}`.replace(/\.txt$/u, "");
281
+ }
282
+
283
+ main().catch((error) => {
284
+ console.error(error instanceof Error ? error.message : String(error));
285
+ process.exitCode = 1;
286
+ });
@@ -0,0 +1,213 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { execFile } from "node:child_process";
4
+ import { createHash } from "node:crypto";
5
+ import { createReadStream, createWriteStream } from "node:fs";
6
+ import { access, appendFile, chmod, copyFile, mkdir, mkdtemp, rename, rm, stat } from "node:fs/promises";
7
+ import { get } from "node:https";
8
+ import { homedir, tmpdir } from "node:os";
9
+ import { dirname, join, resolve } from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+ import { promisify } from "node:util";
12
+
13
+ const execFileAsync = promisify(execFile);
14
+ const SHARK_VERSION = process.env.OPTIMUS_HPROF_SHARK_VERSION?.trim() || "2.14";
15
+ const ARCHIVE_NAME = `shark-cli-${SHARK_VERSION}.zip`;
16
+ const ARCHIVE_DIR_NAME = `shark-cli-${SHARK_VERSION}`;
17
+ const DEFAULT_ARCHIVE_URL = `https://github.com/square/leakcanary/releases/download/shark-cli-${SHARK_VERSION}/${ARCHIVE_NAME}`;
18
+ const DEFAULT_ARCHIVE_SHA256 = "4a1022a4610fd6a4a1306b264f95985c4210e169e2bd4b0ad19bbdcc16d6beef";
19
+ const SCRIPT_DIR = resolve(dirname(fileURLToPath(import.meta.url)));
20
+ const LOG_PREFIX = "[optimus][hprof-runtime]";
21
+ let logFilePathPromise;
22
+
23
+ async function main() {
24
+ const cacheRoot = resolve(
25
+ process.env.OPTIMUS_HPROF_CACHE_DIR?.trim()
26
+ || join(process.env.HOME?.trim() || homedir(), ".optimus", "tools", "shark")
27
+ );
28
+ const archiveUrl = process.env.OPTIMUS_HPROF_SHARK_URL?.trim() || DEFAULT_ARCHIVE_URL;
29
+ const archiveSha256 = process.env.OPTIMUS_HPROF_SHARK_SHA256?.trim() || DEFAULT_ARCHIVE_SHA256;
30
+ const archivePathOverride = process.env.OPTIMUS_HPROF_SHARK_ARCHIVE?.trim();
31
+ const installDir = join(cacheRoot, ARCHIVE_DIR_NAME);
32
+ const runnerPath = join(installDir, "bin", "shark-cli");
33
+
34
+ if (await isExecutableFile(runnerPath)) {
35
+ logInfo(`Using cached Shark runtime at ${installDir}.`);
36
+ process.stdout.write(runnerPath);
37
+ return;
38
+ }
39
+
40
+ await mkdir(cacheRoot, { recursive: true });
41
+ const tempRoot = await mkdtemp(join(tmpdir(), "optimus-shark-install-"));
42
+ logInfo(`Preparing Shark runtime ${SHARK_VERSION} under ${cacheRoot}.`);
43
+
44
+ try {
45
+ const archivePath = archivePathOverride ? resolve(archivePathOverride) : join(tempRoot, ARCHIVE_NAME);
46
+ if (!archivePathOverride) {
47
+ logInfo(`Downloading Shark runtime ${SHARK_VERSION} from ${archiveUrl}.`);
48
+ await downloadArchive(archiveUrl, archivePath);
49
+ logInfo(`Downloaded Shark archive to ${archivePath}.`);
50
+ } else {
51
+ logInfo(`Using provided Shark archive at ${archivePath}.`);
52
+ }
53
+ logInfo(`Verifying Shark archive checksum (${archiveSha256.slice(0, 12)}...).`);
54
+ await verifyArchiveChecksum(archivePath, archiveSha256);
55
+ logInfo("Checksum verified.");
56
+
57
+ const extractRoot = join(tempRoot, "extract");
58
+ await mkdir(extractRoot, { recursive: true });
59
+ logInfo(`Extracting Shark archive into ${extractRoot}.`);
60
+ await extractArchive(archivePath, extractRoot);
61
+ logInfo("Archive extracted.");
62
+
63
+ const extractedDir = join(extractRoot, ARCHIVE_DIR_NAME);
64
+ const extractedRunnerPath = join(extractedDir, "bin", "shark-cli");
65
+ if (!(await isExecutableFile(extractedRunnerPath))) {
66
+ throw new Error(`Downloaded Shark archive did not contain an executable runner at ${extractedRunnerPath}.`);
67
+ }
68
+ await chmod(extractedRunnerPath, 0o755);
69
+
70
+ const stagingDir = join(cacheRoot, `.installing-${Date.now()}-${process.pid}`);
71
+ await rm(stagingDir, { recursive: true, force: true });
72
+ await rename(extractedDir, stagingDir);
73
+ try {
74
+ await rename(stagingDir, installDir);
75
+ logInfo(`Installed Shark runtime at ${installDir}.`);
76
+ } catch (error) {
77
+ if (await isExecutableFile(runnerPath)) {
78
+ await rm(stagingDir, { recursive: true, force: true });
79
+ logInfo(`Another process finished installation first; reusing cached runtime at ${installDir}.`);
80
+ } else {
81
+ throw error;
82
+ }
83
+ }
84
+
85
+ process.stdout.write(runnerPath);
86
+ } finally {
87
+ await rm(tempRoot, { recursive: true, force: true });
88
+ }
89
+ }
90
+
91
+ async function downloadArchive(url, destinationPath) {
92
+ if (url.startsWith("file://")) {
93
+ await copyFile(fileURLToPath(url), destinationPath);
94
+ return;
95
+ }
96
+ if (!url.startsWith("https://")) {
97
+ throw new Error(`Unsupported Shark archive URL: ${url}`);
98
+ }
99
+
100
+ await new Promise((resolvePromise, rejectPromise) => {
101
+ const request = get(url, (response) => {
102
+ if (response.statusCode && response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
103
+ response.resume();
104
+ downloadArchive(response.headers.location, destinationPath).then(resolvePromise, rejectPromise);
105
+ return;
106
+ }
107
+ if (response.statusCode !== 200) {
108
+ response.resume();
109
+ rejectPromise(new Error(`download_failed: HTTP ${response.statusCode ?? "unknown"}`));
110
+ return;
111
+ }
112
+
113
+ const output = createWriteStream(destinationPath);
114
+ output.on("finish", () => {
115
+ output.close();
116
+ resolvePromise(undefined);
117
+ });
118
+ output.on("error", rejectPromise);
119
+ response.on("error", rejectPromise);
120
+ response.pipe(output);
121
+ });
122
+ request.on("error", rejectPromise);
123
+ });
124
+ }
125
+
126
+ async function verifyArchiveChecksum(archivePath, expectedSha256) {
127
+ const actualSha256 = await computeSha256(archivePath);
128
+ if (actualSha256 !== expectedSha256.toLowerCase()) {
129
+ throw new Error(`checksum_mismatch: expected ${expectedSha256}, received ${actualSha256}`);
130
+ }
131
+ }
132
+
133
+ async function computeSha256(filePath) {
134
+ const hash = createHash("sha256");
135
+ await new Promise((resolvePromise, rejectPromise) => {
136
+ const input = createReadStream(filePath);
137
+ input.on("data", (chunk) => hash.update(chunk));
138
+ input.on("end", resolvePromise);
139
+ input.on("error", rejectPromise);
140
+ });
141
+ return hash.digest("hex");
142
+ }
143
+
144
+ async function extractArchive(archivePath, extractRoot) {
145
+ try {
146
+ await execFileAsync("unzip", ["-oq", archivePath, "-d", extractRoot], {
147
+ cwd: SCRIPT_DIR,
148
+ maxBuffer: 16 * 1024 * 1024
149
+ });
150
+ } catch (error) {
151
+ throw new Error(`extract_failed: ${formatProcessError(error)}`.trim());
152
+ }
153
+ }
154
+
155
+ function formatProcessError(error) {
156
+ if (!error || typeof error !== "object") {
157
+ return "";
158
+ }
159
+ const stdout = "stdout" in error && error.stdout ? String(error.stdout).trim() : "";
160
+ const stderr = "stderr" in error && error.stderr ? String(error.stderr).trim() : "";
161
+ return [stdout, stderr].filter(Boolean).join(" ").slice(0, 500);
162
+ }
163
+
164
+ async function isExecutableFile(filePath) {
165
+ try {
166
+ await access(filePath);
167
+ const info = await stat(filePath);
168
+ return info.isFile();
169
+ } catch {
170
+ return false;
171
+ }
172
+ }
173
+
174
+ function logInfo(message) {
175
+ void writeLogLine(message);
176
+ }
177
+
178
+ main().catch((error) => {
179
+ const message = error instanceof Error ? error.message : String(error);
180
+ void writeLogLine(`Failed to prepare Shark runtime: ${message}`);
181
+ process.exitCode = 1;
182
+ });
183
+
184
+ async function writeLogLine(message) {
185
+ const line = `${LOG_PREFIX} ${message}`;
186
+ process.stderr.write(`${line}\n`);
187
+ try {
188
+ const logPath = await resolveLogFilePath();
189
+ await mkdir(dirname(logPath), { recursive: true });
190
+ await appendFile(logPath, `${new Date().toISOString()} ${line}\n`, "utf8");
191
+ } catch {
192
+ // Best-effort logging only; stderr remains the primary fallback.
193
+ }
194
+ }
195
+
196
+ function resolveLogFilePath() {
197
+ if (!logFilePathPromise) {
198
+ const homeDir = process.env.HOME?.trim() || homedir();
199
+ const runtimeLogsDir = process.env.OPTIMUS_RUNTIME_LOGS_DIR?.trim()
200
+ ? resolve(process.env.OPTIMUS_RUNTIME_LOGS_DIR)
201
+ : join(homeDir, ".optimus", "runtime", "logs");
202
+ logFilePathPromise = Promise.resolve(join(runtimeLogsDir, `runtime-${currentDatePart()}.log`));
203
+ }
204
+ return logFilePathPromise;
205
+ }
206
+
207
+ function currentDatePart() {
208
+ const now = new Date();
209
+ const year = String(now.getFullYear());
210
+ const month = String(now.getMonth() + 1).padStart(2, "0");
211
+ const day = String(now.getDate()).padStart(2, "0");
212
+ return `${year}-${month}-${day}`;
213
+ }
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
5
+ ENSURE_SCRIPT="${SCRIPT_DIR}/ensure-shark-runtime.mjs"
6
+
7
+ if [[ -n "${OPTIMUS_HPROF_SHARK_RUNNER:-}" ]]; then
8
+ RUNNER="${OPTIMUS_HPROF_SHARK_RUNNER}"
9
+ else
10
+ if ! command -v node >/dev/null 2>&1; then
11
+ echo "Node.js is required to provision the embedded Shark runtime but was not found in PATH." >&2
12
+ exit 2
13
+ fi
14
+ RUNNER="$(node "${ENSURE_SCRIPT}")"
15
+ fi
16
+
17
+ if [[ ! -x "${RUNNER}" ]]; then
18
+ echo "Embedded Shark runner is unavailable: ${RUNNER}" >&2
19
+ exit 2
20
+ fi
21
+
22
+ if ! command -v java >/dev/null 2>&1; then
23
+ echo "Java runtime is required for embedded Shark analysis but was not found in PATH." >&2
24
+ exit 3
25
+ fi
26
+
27
+ exec "${RUNNER}" "$@"
@@ -0,0 +1,6 @@
1
+ {
2
+ "id": "android-hprof-analyzer",
3
+ "level": "task",
4
+ "version": "1.0.0",
5
+ "taskTypes": ["bugfix"]
6
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sireai/optimus",
3
- "version": "0.1.34",
3
+ "version": "0.1.37",
4
4
  "description": "Optimus Codex-native background task runtime and harness scaffolding.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -5,6 +5,8 @@
5
5
  - Do not present guesses, plans, recommendations, or unexecuted steps as completed facts.
6
6
  - Do not claim a fix without evidence.
7
7
  - If you ran checks, tests, builds, runtime validation, or manual verification, report what ran, how, what happened, and what remains unverified.
8
+ - If available evidence contains any file whose basename includes `hprof`, do not skip heap-dump analysis before concluding a memory leak.
9
+ - Do not prefer screenshot-only or description-only leak reasoning over available HPROF evidence.
8
10
 
9
11
  ## Patch rules
10
12
  - Change code only after reasoning through module boundaries, call chains, state flow, and upstream/downstream impact.
@@ -20,6 +20,10 @@ Typical cases:
20
20
  - application code, scripts, configuration, build logic, or tests
21
21
  - crashes, runtime errors, incorrect behavior, state bugs, and boundary-condition defects
22
22
 
23
+ ## Evidence priority
24
+ - If available evidence contains any file whose basename includes `hprof`, analyze that heap dump before claiming a memory-leak root cause.
25
+ - Treat generated heap-analysis artifacts as primary evidence for memory-retention conclusions.
26
+
23
27
  ## Non-goals
24
28
  You are not:
25
29
  - the triage agent deciding whether the task should be accepted
@@ -93,6 +93,7 @@ Never overstate:
93
93
  - If code changed, runtime should also emit `patch.diff`.
94
94
  - If `patch.diff` exists, `Closure Level` must not be `Analysis Only`.
95
95
  - If `patch.diff` exists, Patch Closure Mode is mandatory.
96
+ - If available evidence contains any file whose basename includes `hprof`, state whether the dump was analyzed and identify the strongest file used.
96
97
  - Before writing `result.md`, determine `Closure Level`, then follow exactly one language mode:
97
98
  - `Verified Fix` or `Repair Candidate`: Patch Closure Mode; all narrative sections are English
98
99
  - `Analysis Only`: Analysis Closure Mode; narrative sections are Chinese
@@ -0,0 +1,94 @@
1
+ # ACCEPT
2
+
3
+ ## Decision target
4
+ Route requirement-to-prototype tasks into the `pm` harness.
5
+
6
+ Triage must decide:
7
+ 1. task type fit
8
+ 2. execution admission
9
+
10
+ The runner, not triage, decides whether final closure is `Prototype Complete`, `Prototype Partial`, or `Analysis Only`.
11
+
12
+ ## Minimum input contract
13
+ Treat the task as execution-ready when the request provides a usable requirement basis plus enough structure to prototype one bounded flow.
14
+
15
+ Recommended PM input vocabulary:
16
+ - `requirement_document`: the requirement basis, usually carried by manual submit `description`, `description-file`, or explicit `requirement-doc-file`
17
+ - `product_goal`: the product or business objective
18
+ - `target_user`: the primary user or audience
19
+ - `core_flow`: the main interaction path to prototype
20
+ - `prototype_scope`: the bounded slice to cover in one task
21
+ - `constraints`: platform, channel, or prototype limits when they materially affect output
22
+
23
+ Admission rule:
24
+ - `requirement_document` must be present
25
+ - at least one concrete `core_flow` must be explicit or clearly derivable from the requirement basis
26
+ - `prototype_scope` must already be bounded in the request or easy to bound without inventing product strategy
27
+ - `product_goal` and `target_user` should be present when they affect flow framing, prioritization, or screen meaning
28
+ - `constraints` are required only when platform or delivery limits materially change the prototype
29
+
30
+ ## Task type fit
31
+ Classify as `pm` only when all are true:
32
+ - the request is to turn requirement input into an interactive HTML prototype
33
+ - the expected output is a prototype artifact, not production code
34
+ - the task centers on flow, structure, interaction, or state presentation
35
+ - the prototype can be derived from requirement input without real system implementation
36
+
37
+ Do not classify as `pm` when any are true:
38
+ - the request is only strategy discussion, prioritization, or product advice
39
+ - the request is for production frontend/backend implementation
40
+ - the request is only visual design refinement with no requirement-to-prototype goal
41
+ - the request is only PRD writing or requirement analysis with no interactive output expectation
42
+ - the request is a bugfix, code-change, or repository task
43
+
44
+ ## Execution admission
45
+ Accept into execution only when all are true:
46
+ - the input provides a usable requirement basis
47
+ - the input provides at least one concrete goal
48
+ - the input provides at least one concrete flow, page path, or interaction path
49
+ - the prototype scope is bounded enough for one task
50
+ - the task does not depend on repository coupling or production-system integration
51
+
52
+ ## Still acceptable with partial information
53
+ Still acceptable when:
54
+ - some states, rules, copy, or edge cases are missing
55
+ - but the main objective and at least one core flow are clear
56
+ - and missing detail can be surfaced as assumptions rather than hidden invention
57
+
58
+ ## Reject for insufficient execution context
59
+ Reject even if the task fits `pm` when any are true:
60
+ - there is no concrete requirement, scenario, or flow to prototype
61
+ - the input only says "make a prototype" or "design a page" with no clear objective or path
62
+ - multiple unrelated areas are mixed with no bounded scope
63
+ - the input is too abstract to determine what users can do in the prototype
64
+ - the task depends on hidden context not present in the current input
65
+ - the request expects product decisions to be invented from scratch
66
+ - the request expects real implementation instead of prototype behavior
67
+
68
+ ## Missing information to mention first when rejecting
69
+ - `requirement_document`
70
+ - `product_goal`
71
+ - `target_user`
72
+ - `core_flow`
73
+ - `prototype_scope`
74
+ - `constraints`
75
+
76
+ ## Missing information mapping guidance
77
+ - use `requirement_document` when there is no usable requirement basis in description, attachment, or referenced input
78
+ - use `product_goal` when the business or user objective is unclear
79
+ - use `target_user` when the intended user is unknown
80
+ - use `core_flow` when no concrete flow or interaction path is described
81
+ - use `prototype_scope` when the request is too broad for one prototype task
82
+ - use `constraints` when platform, channel, or product limits are required but missing
83
+ - prefer the smallest set of fields that explains the rejection
84
+
85
+ ## Event scope
86
+ - `problem.discovered`
87
+ - `task.submitted_manually`
88
+
89
+ ## Triage guidance
90
+ - separate prototype-task fit from execution readiness
91
+ - accept requirement-driven prototype work, not open-ended consulting
92
+ - prefer one clear prototype objective over broad redesign asks
93
+ - incomplete detail is acceptable if the core flow is still prototype-able
94
+ - triage only decides whether the task enters the `pm` pipeline