mixdog 1.0.12 → 1.1.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/README.md +75 -181
- package/package.json +4 -2
- package/scripts/build-shipped-history.mjs +131 -0
- package/scripts/lib/run-node-tests.mjs +235 -13
- package/scripts/lib/test-failure-records.mjs +5 -0
- package/scripts/lib/test-failure-summary.mjs +9 -3
- package/scripts/lib/test-spawn-ledger.mjs +19 -0
- package/scripts/prune-embedding-runtime.mjs +9 -5
- package/scripts/test-direct.mjs +16 -1
- package/src/agents/advisor/AGENT.md +38 -0
- package/src/agents/advisor/agent.json +6 -0
- package/src/agents/reviewer/AGENT.md +5 -2
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +381 -0
- package/src/defaults/skills/goal-management/SKILL.md +11 -5
- package/src/defaults/skills/setup/SKILL.md +5 -8
- package/src/defaults/skills/setup/references/actions.md +61 -31
- package/src/defaults/skills/setup/references/surfaces.md +12 -10
- package/src/lib/rules-builder.cjs +1 -1
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +3 -1
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +4 -0
- package/src/runtime/agent/orchestrator/config-presets.d.mts +1 -0
- package/src/runtime/agent/orchestrator/config-presets.mjs +44 -0
- package/src/runtime/agent/orchestrator/config-providers-merge.mjs +9 -1
- package/src/runtime/agent/orchestrator/config.mjs +102 -21
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +77 -26
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +44 -14
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +11 -2
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +3 -3
- package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +388 -0
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +92 -109
- package/src/runtime/agent/orchestrator/providers/custom-reasoning.mjs +107 -0
- package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +51 -9
- package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +14 -9
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +51 -11
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +6 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +7 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +12 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +27 -7
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +12 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +6 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/quota-reading.mjs +70 -0
- package/src/runtime/agent/orchestrator/providers/registry.mjs +38 -3
- package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +19 -35
- package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +5 -0
- package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +0 -17
- package/src/runtime/agent/orchestrator/runtime-core/goal-state.mjs +0 -3
- package/src/runtime/agent/orchestrator/runtime-core/goal-store.mjs +0 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-text.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-tool-defs.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -2
- package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +18 -4
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +7 -1
- package/src/runtime/agent/orchestrator/runtime-core/session-route-policy.mjs +16 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +2 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +2 -2
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +7 -7
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +28 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +24 -11
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +2 -6
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +63 -3
- package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +4 -4
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +1 -0
- package/src/runtime/agent/orchestrator/stall-policy.mjs +0 -1
- package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +18 -8
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +19 -20
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +6 -11
- package/src/runtime/agent/orchestrator/tools/builtin/file-version-test-helpers.mjs +11 -0
- package/src/runtime/agent/orchestrator/tools/builtin/file-version.mjs +13 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-single-file-rescue.mjs +8 -5
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-task-output.mjs +25 -12
- package/src/runtime/agent/orchestrator/tools/builtin/raw-content-publication.mjs +17 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +136 -76
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +15 -17
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +11 -10
- package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +5 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +30 -16
- package/src/runtime/agent/orchestrator/tools/builtin/snapshot-helpers.mjs +4 -11
- package/src/runtime/agent/orchestrator/tools/builtin/snapshot-store.mjs +7 -3
- package/src/runtime/agent/orchestrator/tools/builtin/snapshot-validation.mjs +16 -19
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-background.mjs +4 -1
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +14 -4
- package/src/runtime/agent/orchestrator/tools/patch/apply-patch/codex-batch.mjs +58 -8
- package/src/runtime/agent/orchestrator/tools/patch/content-guard.mjs +47 -0
- package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +155 -0
- package/src/runtime/agent/orchestrator/tools/patch/dispatch/native-proofs.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/patch/dispatch/native-snapshots.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +19 -16
- package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +13 -44
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/patch/post-patch-excerpts.mjs +5 -2
- package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +47 -7
- package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +72 -8
- package/src/runtime/agent/orchestrator/tools/patch/section-coalesce.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/patch/sequence/run.mjs +9 -1
- package/src/runtime/agent/orchestrator/tools/patch/sequence/units.mjs +9 -1
- package/src/runtime/agent/orchestrator/tools/patch/sequence.mjs +6 -6
- package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +16 -4
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +35 -14
- package/src/runtime/agent/orchestrator/tools/patch/wave.mjs +42 -4
- package/src/runtime/agent/orchestrator/tools/shell-codepage.mjs +296 -0
- package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +75 -24
- package/src/runtime/channels/lib/config.mjs +3 -8
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +7 -3
- package/src/{tui → runtime/channels}/lib/voice-setup.mjs +38 -22
- package/src/runtime/channels/lib/webhook/request-handler.mjs +42 -8
- package/src/runtime/computer-bridge/actions.mjs +2 -0
- package/src/runtime/effort-judge/judge-client.mjs +12 -3
- package/src/runtime/effort-judge/model-manifest.json +7 -7
- package/src/runtime/effort-judge/previous-human-request.mjs +162 -0
- package/src/runtime/effort-judge/verified-context-input.mjs +10 -0
- package/src/runtime/memory/lib/embedding-provider.mjs +12 -0
- package/src/runtime/memory/lib/http-router/session-routes.mjs +2 -1
- package/src/runtime/memory/lib/memory-config-flags.mjs +3 -2
- package/src/runtime/memory/lib/memory-embed.mjs +5 -0
- package/src/runtime/shared/agent-route-config.mjs +50 -2
- package/src/runtime/shared/bounded-file-read.mjs +166 -0
- package/src/runtime/shared/builtin-grandfathering.mjs +37 -0
- package/src/runtime/shared/config.mjs +24 -4
- package/src/runtime/shared/defaults-separation.mjs +263 -0
- package/src/runtime/shared/llm/anthropic-effort.mjs +4 -0
- package/src/runtime/shared/llm/model-catalog.mjs +23 -0
- package/src/runtime/shared/llm/usage-ledger-rollup.mjs +105 -1
- package/src/runtime/shared/llm/usage-ledger-worker.mjs +2 -1
- package/src/runtime/shared/llm/usage-ledger.mjs +72 -5
- package/src/runtime/shared/onnx-runtime-support.mjs +6 -0
- package/src/runtime/shared/orchestration.mjs +14 -6
- package/src/runtime/shared/projects.mjs +65 -48
- package/src/runtime/shared/provider-accounts.mjs +47 -6
- package/src/runtime/shared/quota-resume-store.mjs +255 -0
- package/src/runtime/shared/shipped-definitions.mjs +275 -0
- package/src/runtime/shared/tool-card-model.golden.json +3 -3
- package/src/runtime/shared/tool-result-summary.mjs +86 -15
- package/src/runtime/shared/transcript-metadata.mjs +2 -0
- package/src/runtime/shared/turn-review-reply.mjs +2 -0
- package/src/runtime/shared/turn-snapshot.mjs +65 -6
- package/src/runtime/shared/turn-worktree-snapshot.mjs +202 -22
- package/src/session-runtime/boot/apis.mjs +19 -0
- package/src/session-runtime/boot/config.mjs +4 -0
- package/src/session-runtime/boot/facade.mjs +4 -1
- package/src/session-runtime/boot/infrastructure.mjs +21 -2
- package/src/session-runtime/boot/session.mjs +3 -0
- package/src/session-runtime/channel-config-api.mjs +5 -0
- package/src/session-runtime/config-lifecycle/config-writers.mjs +3 -0
- package/src/session-runtime/config-lifecycle.mjs +31 -6
- package/src/session-runtime/goal-facade-api.mjs +4 -0
- package/src/session-runtime/lifecycle/inheritance.mjs +28 -6
- package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +3 -0
- package/src/session-runtime/lifecycle/session-switch/resume.mjs +7 -1
- package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +6 -0
- package/src/session-runtime/lifecycle/teardown/session-close.mjs +14 -2
- package/src/session-runtime/media-api.mjs +32 -22
- package/src/session-runtime/model-recency.mjs +14 -0
- package/src/session-runtime/model-route/route-tuning.mjs +3 -5
- package/src/session-runtime/model-route/set-route.mjs +49 -21
- package/src/session-runtime/model-route-api.mjs +2 -1
- package/src/session-runtime/notification-bus.mjs +31 -6
- package/src/session-runtime/provider-auth/login.mjs +6 -0
- package/src/session-runtime/provider-models/catalog-load.mjs +3 -1
- package/src/session-runtime/provider-models/row-hydration.mjs +15 -14
- package/src/session-runtime/quick-model-rows/model-meta.mjs +26 -0
- package/src/session-runtime/quick-model-rows/provider-rows.mjs +40 -14
- package/src/session-runtime/resource-api.mjs +8 -1
- package/src/session-runtime/runtime-core.mjs +4 -0
- package/src/session-runtime/runtime-facade.mjs +20 -1
- package/src/session-runtime/runtime-review-api.mjs +12 -1
- package/src/session-runtime/services/agent-tool/helpers.mjs +4 -0
- package/src/session-runtime/services/agent-tool/index-write-queue.mjs +10 -15
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +11 -3
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +2 -0
- package/src/session-runtime/services/channel-admin.mjs +87 -13
- package/src/session-runtime/services/provider-admin.mjs +256 -63
- package/src/session-runtime/services/seeds.mjs +73 -142
- package/src/session-runtime/services/usage-dashboard-model.mjs +29 -0
- package/src/session-runtime/services/usage-dashboard.mjs +7 -0
- package/src/session-runtime/session-address.mjs +125 -0
- package/src/session-runtime/session-create.mjs +57 -2
- package/src/session-runtime/settings-builtin-tools-api.mjs +8 -0
- package/src/session-runtime/settings-compaction-api.mjs +13 -0
- package/src/session-runtime/setup-tool/executor.mjs +92 -40
- package/src/session-runtime/setup-tool/extended-actions.mjs +64 -5
- package/src/session-runtime/setup-tool/settings-contract.mjs +44 -1
- package/src/session-runtime/setup-tool/tool-defs.mjs +12 -7
- package/src/session-runtime/tool-surface.mjs +16 -9
- package/src/session-runtime/turn/session-ops.mjs +3 -0
- package/src/session-runtime/turn/share-ops.mjs +4 -1
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +6 -0
- package/src/session-runtime/usage-stats-api.mjs +15 -0
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +31 -5
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +4 -0
- package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +27 -7
- package/src/standalone/daemon-boot-coordinator.mjs +10 -0
- package/src/standalone/daemon-drain-policy.mjs +9 -0
- package/src/standalone/daemon-stored-session-views.mjs +7 -8
- package/src/standalone/daemon.mjs +21 -5
- package/src/standalone/local-session-runtime.mjs +11 -0
- package/src/standalone/session-client.mjs +52 -7
- package/src/standalone/session-protocol.mjs +8 -0
- package/src/standalone/session-service/agent-tree/agent-child.mjs +2 -0
- package/src/standalone/session-service/entries/lifecycle.mjs +2 -0
- package/src/standalone/session-service/entries/session-loads.mjs +9 -0
- package/src/standalone/session-service/projection/revisions.mjs +28 -4
- package/src/standalone/session-service/projection.mjs +1 -0
- package/src/standalone/session-service/quota-resume.mjs +140 -0
- package/src/standalone/session-service/session-calls/session-action.mjs +2 -2
- package/src/standalone/session-service/session-calls.mjs +10 -2
- package/src/standalone/session-service/stored-reader.mjs +26 -1
- package/src/standalone/session-service.mjs +21 -0
- package/src/standalone/session-transport/routes.mjs +2 -0
- package/src/tui/app/doctor.mjs +7 -11
- package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +3 -5
- package/src/tui/app/settings-picker/settings-rows.mjs +4 -6
- package/src/tui/app/settings-picker/settings-toggles/store-toggles.mjs +1 -1
- package/src/tui/app/slash-dispatch/route-commands.mjs +3 -2
- package/src/tui/components/ContextPanel.jsx +5 -1
- package/src/tui/components/Spinner.jsx +34 -319
- package/src/tui/components/SpinnerByline.jsx +256 -0
- package/src/tui/components/use-spinner-progress.mjs +113 -0
- package/src/tui/dist/index.mjs +15540 -14452
- package/src/tui/session/agent-job-feed/execution-delivery.mjs +13 -1
- package/src/tui/session/agent-job-feed/notification-router.mjs +3 -0
- package/src/tui/session/agent-job-feed/pending-resume.mjs +14 -1
- package/src/tui/session/agent-job-feed.mjs +4 -0
- package/src/tui/session/context-state.mjs +13 -1
- package/src/tui/session/initial-state.mjs +6 -0
- package/src/tui/session/quota-resume.mjs +263 -0
- package/src/tui/session/session-api/intake/abort.mjs +39 -35
- package/src/tui/session/session-api/intake/submission.mjs +13 -21
- package/src/tui/session/session-api/intake.mjs +3 -3
- package/src/tui/session/session-api/integrations.mjs +5 -1
- package/src/tui/session/session-api/lifecycle/dispose.mjs +1 -0
- package/src/tui/session/session-api/lifecycle/inheritance.mjs +2 -2
- package/src/tui/session/session-api/lifecycle/session-reset.mjs +4 -0
- package/src/tui/session/session-api/lifecycle.mjs +71 -2
- package/src/tui/session/session-api/media.mjs +2 -2
- package/src/tui/session/session-api/quota-resume.mjs +21 -0
- package/src/tui/session/session-api/routes.mjs +2 -0
- package/src/tui/session/session-api/settings.mjs +1 -0
- package/src/tui/session/session-api.mjs +2 -0
- package/src/tui/session/session-flow/auto-clear.mjs +14 -6
- package/src/tui/session/session-flow/drain.mjs +52 -5
- package/src/tui/session/session-flow/queue/take-entries.mjs +2 -3
- package/src/tui/session/session-flow/queue.mjs +3 -0
- package/src/tui/session/session-flow/steering.mjs +11 -0
- package/src/tui/session/session-flow.mjs +1 -0
- package/src/tui/session/tool-approval.mjs +7 -2
- package/src/tui/session/transcript-intake.mjs +1 -0
- package/src/tui/session/turn.mjs +40 -3
- package/src/vendor/statusline/bin/statusline-route.mjs +3 -2
|
@@ -2,10 +2,12 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import { appendFile, mkdtemp, readdir, readFile, rm, stat } from 'node:fs/promises';
|
|
3
3
|
import { availableParallelism, tmpdir } from 'node:os';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
|
-
import {
|
|
5
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
6
|
+
import { FAILURE_RECORDS_ENV, spawnLedgerPath } from './test-failure-records.mjs';
|
|
6
7
|
import { classifyRerunOutcomes, formatFailureSummary } from './test-failure-summary.mjs';
|
|
7
8
|
|
|
8
9
|
const SUMMARY_REPORTER = new URL('./test-summary-reporter.mjs', import.meta.url).href;
|
|
10
|
+
const SPAWN_LEDGER = new URL('./test-spawn-ledger.mjs', import.meta.url).href;
|
|
9
11
|
|
|
10
12
|
// A whole-suite run passes ~700 paths to one node process. Windows caps a
|
|
11
13
|
// command line at 32767 characters (CreateProcess) and POSIX caps argv+env at
|
|
@@ -81,17 +83,212 @@ export function testConcurrencyArg(nodeArgs, env = process.env, parallelism = av
|
|
|
81
83
|
return `--test-concurrency=${Math.max(2, Math.floor(parallelism / cap))}`;
|
|
82
84
|
}
|
|
83
85
|
|
|
84
|
-
|
|
86
|
+
// A batch that has not exited by then is killed with its process tree and
|
|
87
|
+
// fails the run; MIXDOG_TEST_BATCH_TIMEOUT_MS overrides the default.
|
|
88
|
+
const DEFAULT_BATCH_TIMEOUT_MS = 60 * 60 * 1000;
|
|
89
|
+
const minutes = (ms) => Number((ms / 60_000).toFixed(2));
|
|
90
|
+
|
|
91
|
+
// Runs a helper command to completion and returns its stdout.
|
|
92
|
+
function capture(command, args) {
|
|
85
93
|
return new Promise((resolve) => {
|
|
86
|
-
const child = spawn(
|
|
94
|
+
const child = spawn(command, args, { stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true });
|
|
95
|
+
let out = '';
|
|
96
|
+
child.stdout.setEncoding('utf8');
|
|
97
|
+
child.stdout.on('data', (chunk) => {
|
|
98
|
+
out += chunk;
|
|
99
|
+
});
|
|
100
|
+
child.on('error', (error) => {
|
|
101
|
+
console.error(`${command} failed: ${error.message}`);
|
|
102
|
+
resolve('');
|
|
103
|
+
});
|
|
104
|
+
child.on('close', () => resolve(out));
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Kills a process and every descendant still linked to it. On POSIX a batch
|
|
109
|
+
// leads its own process group (spawned detached), so the group goes with it.
|
|
110
|
+
async function killTree(pid) {
|
|
111
|
+
if (process.platform === 'win32') {
|
|
112
|
+
await capture('taskkill', ['/T', '/F', '/PID', String(pid)]);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
for (const target of [-pid, pid]) {
|
|
116
|
+
try {
|
|
117
|
+
process.kill(target, 'SIGKILL');
|
|
118
|
+
} catch {
|
|
119
|
+
/* already gone */
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Every live process: { pid, ppid, created (epoch ms), exe ('' when unknown) }.
|
|
125
|
+
// POSIX ps only reports elapsed seconds, so its creation times are ±1 s.
|
|
126
|
+
async function processSnapshot() {
|
|
127
|
+
if (process.platform === 'win32') {
|
|
128
|
+
const out = await capture('powershell', [
|
|
129
|
+
'-NoProfile',
|
|
130
|
+
'-NonInteractive',
|
|
131
|
+
'-Command',
|
|
132
|
+
"Get-CimInstance Win32_Process | Where-Object CreationDate | ForEach-Object { '{0} {1} {2} {3}' -f $_.ProcessId, $_.ParentProcessId, ([DateTimeOffset]$_.CreationDate).ToUnixTimeMilliseconds(), $_.ExecutablePath }",
|
|
133
|
+
]);
|
|
134
|
+
return out
|
|
135
|
+
.split(/\r?\n/)
|
|
136
|
+
.filter(Boolean)
|
|
137
|
+
.map((line) => {
|
|
138
|
+
const [pid, ppid, created, ...exe] = line.split(' ');
|
|
139
|
+
return { pid: Number(pid), ppid: Number(ppid), created: Number(created), exe: exe.join(' ') };
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
const now = Date.now();
|
|
143
|
+
const out = await capture('ps', ['-A', '-o', 'pid=,ppid=,etime=']);
|
|
144
|
+
return out
|
|
145
|
+
.split('\n')
|
|
146
|
+
.map((line) => line.trim().split(/\s+/))
|
|
147
|
+
.filter((fields) => fields.length === 3)
|
|
148
|
+
.map(([pid, ppid, etime]) => {
|
|
149
|
+
// [[dd-]hh:]mm:ss
|
|
150
|
+
const [days, clock] = etime.includes('-') ? etime.split('-') : ['0', etime];
|
|
151
|
+
const seconds = clock.split(':').reduce((total, part) => total * 60 + Number(part), Number(days) * 86_400);
|
|
152
|
+
return { pid: Number(pid), ppid: Number(ppid), created: now - seconds * 1000, exe: '' };
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const CLOCK_SLACK_MS = 2000;
|
|
157
|
+
|
|
158
|
+
// The processes this run owns, out of a snapshot: an anchor (the batch, or a
|
|
159
|
+
// child a test process recorded in its spawn ledger) still alive under its
|
|
160
|
+
// pid, the orphans an exited anchor left (they still name it as their parent;
|
|
161
|
+
// a later process that reuses its pid only has younger children), every
|
|
162
|
+
// descendant of those, and anything executing from the run's scratch root
|
|
163
|
+
// (e.g. the postgres the memory runtime downloads into MIXDOG_DATA_DIR).
|
|
164
|
+
// Command-line text never makes a process the run's: an editor or terminal
|
|
165
|
+
// opened on the scratch root is not.
|
|
166
|
+
export function ownedProcesses(snapshot, anchors, { batchStart, scratchDir, platform = process.platform }) {
|
|
167
|
+
const byPid = new Map(snapshot.map((entry) => [entry.pid, entry]));
|
|
168
|
+
const childrenOf = new Map();
|
|
169
|
+
for (const entry of snapshot) childrenOf.set(entry.ppid, [...(childrenOf.get(entry.ppid) ?? []), entry]);
|
|
170
|
+
const owned = new Map();
|
|
171
|
+
const queue = [];
|
|
172
|
+
const claim = (entry) => {
|
|
173
|
+
if (entry.pid === process.pid || owned.has(entry.pid)) return;
|
|
174
|
+
owned.set(entry.pid, entry);
|
|
175
|
+
queue.push(entry);
|
|
176
|
+
};
|
|
177
|
+
for (const { pid, since } of anchors) {
|
|
178
|
+
const holder = byPid.get(pid);
|
|
179
|
+
if (holder && holder.created >= batchStart - CLOCK_SLACK_MS && holder.created <= since + CLOCK_SLACK_MS) {
|
|
180
|
+
claim(holder);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
for (const orphan of childrenOf.get(pid) ?? []) {
|
|
184
|
+
if (orphan.created >= since - CLOCK_SLACK_MS && (!holder || orphan.created < holder.created)) claim(orphan);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const fold = (path) => (platform === 'win32' ? path.toLowerCase() : path);
|
|
188
|
+
const scratchPrefix = fold(join(scratchDir, '/'));
|
|
189
|
+
for (const entry of snapshot) if (entry.exe && fold(entry.exe).startsWith(scratchPrefix)) claim(entry);
|
|
190
|
+
while (queue.length > 0) {
|
|
191
|
+
const parent = queue.pop();
|
|
192
|
+
for (const entry of childrenOf.get(parent.pid) ?? []) {
|
|
193
|
+
if (entry.created >= parent.created - CLOCK_SLACK_MS) claim(entry);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return [...owned.values()];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async function readSpawnLedger(path) {
|
|
200
|
+
const text = await readFile(path, 'utf8').catch(() => '');
|
|
201
|
+
return text
|
|
202
|
+
.split('\n')
|
|
203
|
+
.filter(Boolean)
|
|
204
|
+
.map((line) => {
|
|
205
|
+
const [pid, since] = line.split(' ').map(Number);
|
|
206
|
+
return { pid, since };
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// A process a test leaves running must not outlive its batch: it can hold a
|
|
211
|
+
// port, a database, or files in the scratch root the run removes at the end.
|
|
212
|
+
async function reapLeftovers(child, { batchStart, ledger, scratchDir }) {
|
|
213
|
+
// The batch itself has exited: only its process group can remain.
|
|
214
|
+
if (process.platform !== 'win32' && child.pid) {
|
|
215
|
+
try {
|
|
216
|
+
process.kill(-child.pid, 'SIGKILL');
|
|
217
|
+
} catch {
|
|
218
|
+
/* the group is empty */
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
const anchors = [...(child.pid ? [{ pid: child.pid, since: batchStart }] : []), ...(await readSpawnLedger(ledger))];
|
|
222
|
+
const leftovers = ownedProcesses(await processSnapshot(), anchors, { batchStart, scratchDir });
|
|
223
|
+
if (leftovers.length === 0) return;
|
|
224
|
+
const pids = leftovers.map((entry) => entry.pid);
|
|
225
|
+
console.error(`Killing ${pids.length} process(es) a test left running: ${pids.join(' ')}`);
|
|
226
|
+
if (process.platform === 'win32') {
|
|
227
|
+
await capture('taskkill', ['/T', '/F', ...pids.flatMap((pid) => ['/PID', String(pid)])]);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
for (const pid of pids) {
|
|
231
|
+
try {
|
|
232
|
+
process.kill(pid, 'SIGKILL');
|
|
233
|
+
} catch {
|
|
234
|
+
/* already gone */
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Resolves { code, hung } once the batch exited and its leftovers are gone.
|
|
240
|
+
function spawnBatch(args, env, { files, ledger, scratchDir, timeoutMs }) {
|
|
241
|
+
return new Promise((resolve) => {
|
|
242
|
+
const batchStart = Date.now();
|
|
243
|
+
const child = spawn(process.execPath, args, { stdio: 'inherit', env, detached: process.platform !== 'win32' });
|
|
244
|
+
let hung = '';
|
|
245
|
+
const watchdog = setTimeout(() => {
|
|
246
|
+
hung = `hung after ${minutes(timeoutMs)} min: ${files.join(' ') || '(discovered files)'}`;
|
|
247
|
+
console.error(`Test batch ${hung}; killing its process tree`);
|
|
248
|
+
void killTree(child.pid);
|
|
249
|
+
}, timeoutMs);
|
|
250
|
+
// A detached POSIX batch no longer gets the terminal's signals itself.
|
|
251
|
+
const forward = (signal) => {
|
|
252
|
+
try {
|
|
253
|
+
process.kill(-child.pid, signal);
|
|
254
|
+
} catch {
|
|
255
|
+
/* already gone */
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
const signals = process.platform === 'win32' ? [] : ['SIGINT', 'SIGTERM'];
|
|
259
|
+
for (const signal of signals) process.on(signal, forward);
|
|
260
|
+
const settle = async (code) => {
|
|
261
|
+
clearTimeout(watchdog);
|
|
262
|
+
for (const signal of signals) process.off(signal, forward);
|
|
263
|
+
await reapLeftovers(child, { batchStart, ledger, scratchDir });
|
|
264
|
+
resolve({ code: hung ? 1 : code, hung });
|
|
265
|
+
};
|
|
87
266
|
child.on('error', (error) => {
|
|
88
267
|
console.error(error);
|
|
89
|
-
|
|
268
|
+
void settle(1);
|
|
90
269
|
});
|
|
91
|
-
child.on('exit', (code, signal) =>
|
|
270
|
+
child.on('exit', (code, signal) => void settle(code ?? (signal ? 1 : 0)));
|
|
92
271
|
});
|
|
93
272
|
}
|
|
94
273
|
|
|
274
|
+
// rm's own maxRetries re-walks the tree on every nested retry, so one held
|
|
275
|
+
// file a few directories down multiplied its delay into hours. Each pass here
|
|
276
|
+
// is a single bounded walk.
|
|
277
|
+
async function removeScratch(dir) {
|
|
278
|
+
for (let attempt = 1; ; attempt += 1) {
|
|
279
|
+
try {
|
|
280
|
+
await rm(dir, { recursive: true, force: true });
|
|
281
|
+
return;
|
|
282
|
+
} catch (error) {
|
|
283
|
+
if (attempt === 5) {
|
|
284
|
+
console.error(`Test scratch left behind (${error.code}): ${dir}`);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
await delay(200 * attempt);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
95
292
|
// The failure reporter writes one JSON line per failed test; a spawn that
|
|
96
293
|
// died before Node opened its destination leaves no file at all.
|
|
97
294
|
async function readFailureRecords(path) {
|
|
@@ -136,6 +333,8 @@ export async function runNodeTests(
|
|
|
136
333
|
const runArgs = [
|
|
137
334
|
...nodeArgs,
|
|
138
335
|
...(concurrencyArg ? [concurrencyArg] : []),
|
|
336
|
+
// Node's test runner passes --import on to every test file process.
|
|
337
|
+
`--import=${SPAWN_LEDGER}`,
|
|
139
338
|
`--test-reporter=${SUMMARY_REPORTER}`,
|
|
140
339
|
'--test-reporter-destination=stdout',
|
|
141
340
|
'--test-reporter=spec',
|
|
@@ -171,15 +370,25 @@ export async function runNodeTests(
|
|
|
171
370
|
...(coverageDir ? { NODE_V8_COVERAGE: coverageDir } : {}),
|
|
172
371
|
...(nodeOptions ? { NODE_OPTIONS: nodeOptions } : {}),
|
|
173
372
|
});
|
|
373
|
+
const batchOptions = (files, recordsPath) => ({
|
|
374
|
+
files,
|
|
375
|
+
ledger: spawnLedgerPath(recordsPath),
|
|
376
|
+
scratchDir,
|
|
377
|
+
timeoutMs: positiveInt(process.env.MIXDOG_TEST_BATCH_TIMEOUT_MS) ?? DEFAULT_BATCH_TIMEOUT_MS,
|
|
378
|
+
});
|
|
174
379
|
let status = 0;
|
|
175
380
|
const failures = [];
|
|
381
|
+
const hung = [];
|
|
176
382
|
for (const [index, batch] of batches.entries()) {
|
|
177
383
|
const batchLog = batches.length === 1 ? logPath : `${logPath}.${index + 1}`;
|
|
178
384
|
const batchFailures = join(failureDir, `batch-${index + 1}.jsonl`);
|
|
179
|
-
const
|
|
385
|
+
const outcome = await spawnBatch(
|
|
180
386
|
[...runArgs, `--test-reporter-destination=${batchLog}`, ...batch],
|
|
181
|
-
childEnv(batchFailures)
|
|
387
|
+
childEnv(batchFailures),
|
|
388
|
+
batchOptions(batch, batchFailures)
|
|
182
389
|
);
|
|
390
|
+
const { code } = outcome;
|
|
391
|
+
if (outcome.hung) hung.push(outcome.hung);
|
|
183
392
|
if (batchLog !== logPath) {
|
|
184
393
|
await appendFile(logPath, await readFile(batchLog));
|
|
185
394
|
await rm(batchLog, { force: true });
|
|
@@ -196,10 +405,13 @@ export async function runNodeTests(
|
|
|
196
405
|
console.error(`Re-running ${files.length} failed file(s): attempt ${attempt}/${rerunFailed}`);
|
|
197
406
|
const attemptLog = `${logPath}.rerun-${attempt}`;
|
|
198
407
|
const attemptFailures = join(failureDir, `rerun-${attempt}.jsonl`);
|
|
199
|
-
|
|
408
|
+
const outcome = await spawnBatch(
|
|
200
409
|
[...runArgs, `--test-reporter-destination=${attemptLog}`, ...files],
|
|
201
|
-
childEnv(attemptFailures)
|
|
410
|
+
childEnv(attemptFailures),
|
|
411
|
+
batchOptions(files, attemptFailures)
|
|
202
412
|
);
|
|
413
|
+
lastAttemptStatus = outcome.code;
|
|
414
|
+
if (outcome.hung) hung.push(outcome.hung);
|
|
203
415
|
await appendFile(logPath, await readFile(attemptLog));
|
|
204
416
|
await rm(attemptLog, { force: true });
|
|
205
417
|
attempts.push({ files, failures: await readFailureRecords(attemptFailures) });
|
|
@@ -209,17 +421,27 @@ export async function runNodeTests(
|
|
|
209
421
|
// Flaky tolerance applies only to an ordinary test-failure exit that the
|
|
210
422
|
// re-runs fully explained: nothing failed twice and the last re-run itself
|
|
211
423
|
// came back clean.
|
|
212
|
-
|
|
424
|
+
// A hung batch is never explained by re-runs.
|
|
425
|
+
if (
|
|
426
|
+
rerunFailed > 0 &&
|
|
427
|
+
status === 1 &&
|
|
428
|
+
hung.length === 0 &&
|
|
429
|
+
failed.length === 0 &&
|
|
430
|
+
flaky.length > 0 &&
|
|
431
|
+
lastAttemptStatus === 0
|
|
432
|
+
)
|
|
433
|
+
status = 0;
|
|
213
434
|
process.exitCode = status;
|
|
214
435
|
|
|
215
436
|
await rm(failureDir, { recursive: true, force: true });
|
|
216
|
-
//
|
|
217
|
-
|
|
437
|
+
// Leftover processes are already killed; what a process outside the run
|
|
438
|
+
// still holds stays behind.
|
|
439
|
+
await removeScratch(scratchDir);
|
|
218
440
|
if (coverageDir) {
|
|
219
441
|
const { reportCoverage } = await import('./coverage.mjs');
|
|
220
442
|
await reportCoverage(coverageDir);
|
|
221
443
|
await rm(coverageDir, { recursive: true, force: true });
|
|
222
444
|
}
|
|
223
445
|
// Last line of the run, after every reporter and the coverage report.
|
|
224
|
-
process.stdout.write(`\n${formatFailureSummary({ failed, flaky, rerunFailed })}`);
|
|
446
|
+
process.stdout.write(`\n${formatFailureSummary({ failed, flaky, rerunFailed, hung })}`);
|
|
225
447
|
}
|
|
@@ -13,6 +13,11 @@ import { relative } from 'node:path';
|
|
|
13
13
|
|
|
14
14
|
export const FAILURE_RECORDS_ENV = 'MIXDOG_TEST_FAILURE_RECORDS';
|
|
15
15
|
|
|
16
|
+
// Every child process a test process starts is logged beside its spawn's
|
|
17
|
+
// failure records (scripts/lib/test-spawn-ledger.mjs), so the runner can tell
|
|
18
|
+
// a process the run started from one that merely names its directories.
|
|
19
|
+
export const spawnLedgerPath = (recordsPath) => (recordsPath ? `${recordsPath}.spawns` : '');
|
|
20
|
+
|
|
16
21
|
// Ancestor names are tracked per file: node:test runs files concurrently, so
|
|
17
22
|
// one flat stack would mix their nestings.
|
|
18
23
|
export function failureRecord(event, stacks) {
|
|
@@ -43,8 +43,14 @@ export function policyLine(rerunFailed) {
|
|
|
43
43
|
: 'policy: --rerun-failed is off, so every failure fails the run (pass --rerun-failed <n> to classify flaky)';
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
// `hung` holds one "hung after N min: <files>" message per batch the runner's
|
|
47
|
+
// watchdog killed; such a batch fails the run whatever its tests reported.
|
|
48
|
+
export function formatFailureSummary({ failed = [], flaky = [], rerunFailed = 0, hung = [] }) {
|
|
49
|
+
const hungCount = hung.length > 0 ? `, ${hung.length} hung batch(es)` : '';
|
|
50
|
+
const lines = [
|
|
51
|
+
`${SUMMARY_TAG} ${failed.length} failed, ${flaky.length} flaky${hungCount} (every failed test is named below)`,
|
|
52
|
+
];
|
|
53
|
+
for (const message of hung) lines.push(`${SUMMARY_TAG} HUNG ${message}`);
|
|
48
54
|
for (const record of failed) {
|
|
49
55
|
const origin = record.firstSeenOnRerun ? ` (first seen on re-run ${record.firstSeenOnRerun})` : '';
|
|
50
56
|
lines.push(`${SUMMARY_TAG} FAILED ${record.file} > ${record.name}${origin}`);
|
|
@@ -54,6 +60,6 @@ export function formatFailureSummary({ failed = [], flaky = [], rerunFailed = 0
|
|
|
54
60
|
`${SUMMARY_TAG} FLAKY ${record.file} > ${record.name} (failed, then passed on re-run ${record.passedOnAttempt}/${rerunFailed})`
|
|
55
61
|
);
|
|
56
62
|
}
|
|
57
|
-
if (failed.length > 0 || flaky.length > 0) lines.push(`${SUMMARY_TAG} ${policyLine(rerunFailed)}`);
|
|
63
|
+
if (failed.length > 0 || flaky.length > 0 || hung.length > 0) lines.push(`${SUMMARY_TAG} ${policyLine(rerunFailed)}`);
|
|
58
64
|
return `${lines.join('\n')}\n`;
|
|
59
65
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Preloaded (--import) into every test process the runner starts: records
|
|
2
|
+
// "<pid> <spawn time ms>" for each child process, so a process a test leaves
|
|
3
|
+
// running can be traced back to the run after its parent exited
|
|
4
|
+
// (scripts/lib/run-node-tests.mjs). Without the runner's environment it does
|
|
5
|
+
// nothing.
|
|
6
|
+
import { ChildProcess } from 'node:child_process';
|
|
7
|
+
import { appendFileSync } from 'node:fs';
|
|
8
|
+
import { FAILURE_RECORDS_ENV, spawnLedgerPath } from './test-failure-records.mjs';
|
|
9
|
+
|
|
10
|
+
const ledger = spawnLedgerPath(process.env[FAILURE_RECORDS_ENV]);
|
|
11
|
+
if (ledger) {
|
|
12
|
+
// spawn, exec, execFile and fork all start their process here.
|
|
13
|
+
const spawn = ChildProcess.prototype.spawn;
|
|
14
|
+
ChildProcess.prototype.spawn = function recordedSpawn(...args) {
|
|
15
|
+
const result = spawn.apply(this, args);
|
|
16
|
+
if (this.pid) appendFileSync(ledger, `${this.pid} ${Date.now()}\n`);
|
|
17
|
+
return result;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { access, readdir, rm } from 'node:fs/promises';
|
|
4
4
|
import { dirname, join, resolve } from 'node:path';
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
6
|
+
import { onnxRuntimeSupported } from '../src/runtime/shared/onnx-runtime-support.mjs';
|
|
6
7
|
|
|
7
8
|
const SUPPORTED_TARGETS = new Map([
|
|
8
9
|
['win32', new Set(['x64', 'arm64'])],
|
|
@@ -83,8 +84,9 @@ export async function pruneEmbeddingRuntime(packageRoot, options = {}) {
|
|
|
83
84
|
).find(Boolean);
|
|
84
85
|
if (!ortRoot) throw new Error('Embedding runtime is incomplete: onnxruntime-node is unavailable');
|
|
85
86
|
|
|
86
|
-
const
|
|
87
|
-
|
|
87
|
+
const onnx = onnxRuntimeSupported(target.platform, target.arch);
|
|
88
|
+
const targetBinaryDir = onnx ? join(ortRoot, 'bin', 'napi-v6', target.platform, target.arch) : null;
|
|
89
|
+
if (onnx && !(await exists(join(targetBinaryDir, 'onnxruntime_binding.node')))) {
|
|
88
90
|
throw new Error(`Embedding runtime is incomplete: missing ${target.key} ONNX binding`);
|
|
89
91
|
}
|
|
90
92
|
|
|
@@ -100,10 +102,11 @@ export async function pruneEmbeddingRuntime(packageRoot, options = {}) {
|
|
|
100
102
|
// bin/napi-v6/${process.platform}/${process.arch}/onnxruntime_binding.node.
|
|
101
103
|
// Keep one native payload and remove every foreign OS/architecture.
|
|
102
104
|
const napiRoot = join(ortRoot, 'bin', 'napi-v6');
|
|
103
|
-
|
|
104
|
-
await removeChildrenExcept(
|
|
105
|
+
// Targets without an ONNX binding keep no native payload at all.
|
|
106
|
+
await removeChildrenExcept(napiRoot, new Set(onnx ? [target.platform] : []));
|
|
107
|
+
if (onnx) await removeChildrenExcept(join(napiRoot, target.platform), new Set([target.arch]));
|
|
105
108
|
const removedTargetFiles = [];
|
|
106
|
-
if (target.platform === 'win32') {
|
|
109
|
+
if (onnx && target.platform === 'win32') {
|
|
107
110
|
// The fixed production embedding profile uses CPU inference. Keep
|
|
108
111
|
// DirectML.dll for explicit device overrides, but remove the optional DXC
|
|
109
112
|
// shader compiler payload verified unnecessary by packaged-runtime warmup.
|
|
@@ -132,6 +135,7 @@ export async function pruneEmbeddingRuntime(packageRoot, options = {}) {
|
|
|
132
135
|
...target,
|
|
133
136
|
transformerRoot,
|
|
134
137
|
ortRoot,
|
|
138
|
+
onnx,
|
|
135
139
|
targetBinaryDir,
|
|
136
140
|
removedTargetFiles,
|
|
137
141
|
};
|
package/scripts/test-direct.mjs
CHANGED
|
@@ -2,5 +2,20 @@
|
|
|
2
2
|
// Explicit Node test options and file paths, without discovery or lane filters.
|
|
3
3
|
import { runNodeTests } from './lib/run-node-tests.mjs';
|
|
4
4
|
|
|
5
|
+
const argv = process.argv.slice(2);
|
|
6
|
+
// The same defaults as scripts/test.mjs: `mock.module` is still flag-gated on
|
|
7
|
+
// 22/24, and a suite that leaves a handle open must not hang the whole run.
|
|
8
|
+
// A flag the caller already passed (to node or to this script) is not repeated.
|
|
9
|
+
const given = new Set([...process.execArgv, ...argv]);
|
|
10
|
+
const unlessGiven = (flag) => (given.has(flag) ? [] : [flag]);
|
|
11
|
+
|
|
5
12
|
// Preserve parent Node flags such as heap limits and preload modules.
|
|
6
|
-
await runNodeTests(
|
|
13
|
+
await runNodeTests(
|
|
14
|
+
[
|
|
15
|
+
...process.execArgv,
|
|
16
|
+
...unlessGiven('--experimental-test-module-mocks'),
|
|
17
|
+
'--test',
|
|
18
|
+
...unlessGiven('--test-force-exit'),
|
|
19
|
+
],
|
|
20
|
+
argv
|
|
21
|
+
);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Advisor
|
|
3
|
+
description: Use on non-trivial work after orientation and before committing to an approach, when stuck or results do not converge, before changing approach, and for requested second opinions. Skip for low-difficulty work. Advisory analysis only; the final check belongs to Reviewer.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Independent technical advisor.
|
|
7
|
+
|
|
8
|
+
You are consulted at decision points: once orientation is done and before an
|
|
9
|
+
approach is committed, when errors recur or results stop converging, before a
|
|
10
|
+
change of approach, and when the user asks for a second opinion or
|
|
11
|
+
cross-validation. You do not see the Lead's conversation; work from the brief
|
|
12
|
+
and verify its evidence against the source yourself.
|
|
13
|
+
|
|
14
|
+
For design advice, compare the smallest viable alternatives against the
|
|
15
|
+
requested outcome, established project patterns, correctness, compatibility,
|
|
16
|
+
security, performance, and maintenance cost. Recommend one course of action and
|
|
17
|
+
explain the material trade-off. Do not broaden the task into a general audit or
|
|
18
|
+
redesign.
|
|
19
|
+
|
|
20
|
+
For a stuck or non-converging task, identify the most likely cause from the
|
|
21
|
+
actual execution paths and observed behavior, and name the decisive check that
|
|
22
|
+
separates competing explanations. Reproduce counterexamples with bounded tests
|
|
23
|
+
when needed. A passing test is evidence only for the path it exercises.
|
|
24
|
+
|
|
25
|
+
When the brief reports evidence that conflicts with earlier advice, resolve the
|
|
26
|
+
conflict explicitly: state which constraint decides it and why.
|
|
27
|
+
|
|
28
|
+
Work from source and observed behavior, not from the Lead's conclusion. State
|
|
29
|
+
what is confirmed, inferred, or unverified; name missing evidence and concrete
|
|
30
|
+
blockers rather than inventing certainty.
|
|
31
|
+
|
|
32
|
+
Do not implement fixes, modify project files or settings, stage or commit
|
|
33
|
+
changes, deploy, or delegate work. Keep checks non-destructive and isolate any
|
|
34
|
+
test fixtures from existing user data. The final check belongs to Reviewer.
|
|
35
|
+
|
|
36
|
+
Lead with the recommendation, then actionable findings ordered by severity, with
|
|
37
|
+
evidence located by project-relative path and function or symbol. If no issue
|
|
38
|
+
is found, say so and list only material residual risks and verification limits.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "advisor",
|
|
3
|
+
"name": "Advisor",
|
|
4
|
+
"description": "Use on non-trivial work after orientation and before committing to an approach, when stuck or results do not converge, before changing approach, and for requested second opinions. Skip for low-difficulty work. Advisory analysis only; the final check belongs to Reviewer.",
|
|
5
|
+
"entry": "AGENT.md"
|
|
6
|
+
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: Reviewer
|
|
3
|
-
description: Use after implementation to independently verify correctness, regressions, risks, and
|
|
3
|
+
description: Use once at the final check of non-trivial work: after the implementation is written and verified, before reporting completion, to independently verify correctness, regressions, risks, and acceptance criteria. Skip for low-difficulty work.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Independent
|
|
6
|
+
Independent final-check review agent.
|
|
7
|
+
|
|
8
|
+
You are called once, after the deliverable is written and the Lead's own
|
|
9
|
+
verification has run, before completion is reported.
|
|
7
10
|
|
|
8
11
|
Inspect the diff, affected boundaries, existing tests, and stated acceptance
|
|
9
12
|
criteria with independent judgment. Run the necessary builds, tests, lint, or
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "reviewer",
|
|
3
3
|
"name": "Reviewer",
|
|
4
|
-
"description": "Use after implementation to independently verify correctness, regressions, risks, and
|
|
4
|
+
"description": "Use once at the final check of non-trivial work: after the implementation is written and verified, before reporting completion, to independently verify correctness, regressions, risks, and acceptance criteria. Skip for low-difficulty work.",
|
|
5
5
|
"entry": "AGENT.md"
|
|
6
6
|
}
|